【问题标题】:Better, simpler example of 'semantic conflict'?更好、更简单的“语义冲突”示例?
【发布时间】:2011-01-31 15:39:28
【问题描述】:

我喜欢从版本控制系统 (VCS) 中区分三种不同类型的冲突:

  • 文本
  • 句法
  • 语义

文本冲突是由合并或更新过程检测到的。这是由系统标记的。在解决冲突之前,VCS 不允许提交结果。

句法冲突没有被 VCS 标记,但结果不会被编译。因此,即使是稍微细心的程序员也应该了解这一点。 (一个简单的例子可能是由 Left 重命名变量,以及由 Right 使用该变量的一些添加行。合并可能会有一个未解析的符号。或者,这可能会引入一个语义变量隐藏的冲突。)

最后,语义冲突没有被 VCS 标记,结果可以编译,但代码运行可能有问题。在轻微的情况下,会产生不正确的结果。在严重的情况下,可能会导致崩溃。即使是这些也应该由非常细心的程序员在提交之前通过代码审查或单元测试进行检测。

我的语义冲突示例使用 SVN(Subversion)和 C++,但这些选择与问题的本质并不真正相关。

基本代码是:

int i = 0;
int odds = 0;
while (i < 10)
{
    if ((i & 1) != 0)
    {
        odds *= 10;
        odds += i;
    }
    // next
    ++ i;
}
assert (odds == 13579)

左(L)和右(R)变化如下。

的“优化”(改变循环变量的值):

int i = 1; // L
int odds = 0;
while (i < 10)
{
    if ((i & 1) != 0)
    {
        odds *= 10;
        odds += i;
    }
    // next
    i += 2; // L
}
assert (odds == 13579)

的“优化”(改变循环变量的使用方式):

int i = 0;
int odds = 0;
while (i < 5) // R
{
    odds *= 10;
    odds += 2 * i + 1; // R
    // next
    ++ i;
}
assert (odds == 13579)

这是合并或更新的结果,SVN 没有检测到(这对于 VCS 来说是正确的行为),因此它不是文本冲突。请注意,它可以编译,所以它不是语法冲突。

int i = 1; // L
int odds = 0;
while (i < 5) // R
{
    odds *= 10;
    odds += 2 * i + 1; // R
    // next
    i += 2; // L
}
assert (odds == 13579)

assert 失败,因为 odds 是 37。

所以我的问题如下。还有比这更简单的例子吗?有没有编译后的可执行文件出现新崩溃的简单示例?

作为第二个问题,您在实际代码中是否遇到过这种情况?同样,特别欢迎简单的示例。

【问题讨论】:

  • 这与 C++ 无关,因此我删除了该标签。老实说,我不明白这个问题,也看不出它与版本控制有什么关系。
  • rhubbarb 可能想听听其他人对这些语义变化的经验,这样他就可以建立一些在合并代​​码时需要注意的事项清单。如果这确实是重点的话,可能会成为一个有趣的话题。
  • @Tomislav:是的,这就是原因之一。 @Neil:我同意您删除 C++ 标签;我的错。另一方面,这与版本控制有一切。合并操作是 VCS 的核心,了解这些非常有用的系统偶尔会产生意外结果的方式也很重要。
  • @Neil:这让我想起了你在类似主题上的回答:stackoverflow.com/questions/1245431/…
  • 您好,我们听取了您的要求,我们想出了一个工具,很可能解决部分问题:plasticscm.com/sm/index.html,我们现在开始内测。

标签: svn version-control conflict


【解决方案1】:

想出简单的相关例子并不明显,这条评论最好地总结了原因:

如果更改就在附近,那么琐碎的解决方案更有可能是正确的(因为那些不正确的解决方案更有可能触及代码的相同部分,从而导致不平凡的冲突),并且在那些少数在它们不是的情况下,问题将相对较快地表现出来,并且可能以明显的方式表现出来。

[这基本上就是你的例子所说明的]

但是,检测由广泛分离的代码区域中的更改之间的合并引入的语义冲突可能需要比大多数程序员掌握更多的程序 - 或者在内核大小的项目中,比任何程序员都可以。
因此,即使您确实手动查看了这些 3 向差异,这也将是一个相对无用的练习:努力与信心的获得相去甚远。

事实上,我认为合并是一条红鲱鱼:
代码的不同但相互依赖的部分之间的这种语义冲突是不可避免的,因为它们可以分开发展。
这个并发开发过程是如何组织的——DVCS; CVCS;压缩包和补丁;每个人都在网络共享上编辑相同的文件——这与事实无关。
合并不会导致语义冲突,编程会导致语义冲突。

换句话说,我在合并后的真实代码中遇到的语义冲突的真实情况并不简单,而是相当复杂。


话虽如此,如Martin Fowler in his article Feature Branch 所示,最简单的例子是方法重命名:

我更担心的问题是语义冲突。
一个简单的例子是,如果 Plum 教授更改了 Reverend Green 的代码调用的方法的名称。重构工具允许您安全地重命名方法,但仅限于您的代码库。
因此,如果 G1-6 包含调用 foo 的新代码,Plum 教授在他的代码库中无法分辨,因为他没有。你只会发现大合并。

函数重命名是一个比较明显的语义冲突案例。
在实践中,它们可以更加微妙。

测试是发现它们的关键,但是要合并的代码越多,冲突的可能性就越大,解决它们的难度就越大
冲突的风险,尤其是语义冲突,让大合并变得可怕。


正如Ole Lyngehis answer 中提到的(赞成),Martin Fowler 今天(本次编辑时间)确实写了一篇关于“语义冲突”的帖子,包括以下插图:

同样,这是基于函数重命名,即使提到了基于内部函数重构的更微妙的情况:

最简单的例子是重命名函数。
假设我认为 clcBl 方法如果被称为 calculateBill 会更容易使用。

所以这里的第一点是,无论你的工具多么强大,它只会保护你免受文本冲突。

然而,有一些策略可以极大地帮助我们处理它们

  • 第一个是SelfTestingCode。测试正在有效地探测我们的代码,看看他们对代码语义的看法是否与代码实际所做的一致
  • 另一种有用的技术是更频繁地合并

人们经常尝试根据 DVCS 使功能分支变得容易的方式来证明其合理性。但这忽略了语义冲突的问题。
如果您的功能在几天内快速构建,那么您将遇到更少的语义冲突(如果不到一天,那么它实际上与 CI 相同)。但是,我们不会经常看到如此短的功能分支。

我认为需要在快照分支和特征分支之间找到一个中间地带。
如果您在相同功能分支上有一组开发人员,则合并通常是关键。

【讨论】:

  • 提出这个问题的部分原因是我打算做一个关于源代码控制的演讲,特别是关于 SVN 的演讲。我要说明的一点是,没有任何软件工具可以替代良好的计划或良好的沟通。我要说明的另一点是 SVN 做得很好,但它无法读懂你的想法。这就是为什么我想要一个人工简单的例子。感谢您的回答。我仍然希望对我的问题有进一步的答案或 cmets...
  • @rhubbarb:明白了,我觉得你的问题很有趣。关于 SVN,真正的问题当然是合并:见 stackoverflow.com/questions/2475831/merging-hg-git-vs-svn/…stackoverflow.com/questions/2471606/…
  • 哇,我整天都在阅读语义冲突(实际上是为了理解在大多数关于成功的自动合并的快乐谈论中关于它们的病态沉默......),现在这个一句话终于为正确的方向指明了方向:“合并不会导致语义冲突,编程会导致语义冲突。”谢谢! :) (这里是原始评论的位置,在一篇精彩的博文下,顺便说一句:yosefk.com/blog/dvcs-and-its-most-vexing-merge.html
【解决方案2】:

查看 Martin Fowler 在这篇文章中的示例:http://martinfowler.com/bliki/SemanticConflict.html

【讨论】:

  • 优秀的参考。 +1 我已将其包含在我的答案中。
【解决方案3】:

场景:存在方法foo()。两个分支从这里开始。

  1. 分支 1 将 foo() 重命名为 food()
  2. 分支 2 向foo() 添加了一个新呼叫。

当分支 1 和 2 合并时,没有可检测到的冲突。但是,分支 2 对 foo() 的调用现在引用了一个不再存在的方法。

【讨论】:

    猜你喜欢
    • 2016-01-07
    • 2018-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多