更好,更简单的“语义冲突”的例子?

时间:2010-03-25 09:59:55

标签: svn version-control conflict

我喜欢从版本控制系统(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。

所以我的问题如下。有比这更简单的例子吗?是否有一个简单的例子,编译的可执行文件有一个新的崩溃?

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

3 个答案:

答案 0 :(得分:8)

提出简单的相关示例并不明显,此评论总结了最佳原因:

  

如果更改接近,则平凡分辨率更可能是正确的(因为那些不正确的分辨率更可能触及代码的相同部分,从而导致非平凡的冲突),并且在那些少数如果不是这样的话,问题就会以相对较快的速度表现出来,而且可能是显而易见的。

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

  

但是,检测代码中广泛分离的区域中的更改之间的合并引入的语义冲突可能需要比大多数程序员可以在头脑中保存更多的程序 - 或者在内核大小的项目中,比任何程序员都要多。
  因此,即使您手动审查这些三向差异,也将是一项相对无用的练习:这种努力与信心的增加相差无几。

     

事实上,我认为合并是一个红色的鲱鱼:
  代码的不同但相互依存的部分之间的这种语义冲突在它们可以单独发展的那一刻是不可避免的。
  如何组织此并发开发过程 - DVCS; CVCS; tarball和补丁;每个人都在网络共享上编辑相同的文件 - 对这个事实根本没有任何影响   合并不会导致语义冲突,编程会导致语义冲突。

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


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

  

我更担心的问题是语义冲突   一个简单的例子是,如果Plum教授改变了牧师格林代码所称的方法的名称。重构工具允许您安全地重命名方法,但仅限于您的代码库   因此,如果G1-6包含调用foo的新代码,Plum教授无法告诉他的代码库,因为他没有。你只能找到大合并。

     

函数重命名是语义冲突的一个相对明显的例子   在实践中,它们可能更加微妙。
  测试是发现它们的关键,但合并的代码越多,就越有可能发生冲突,修复它们的难度就越大
  冲突的风险,特别是语义冲突,使大合并变得可怕。


Ole Lynge提及his answer(upvoted),Martin Fowler今天(编辑时间)写了一篇关于“语义冲突”的文章,包括以下插图:

semantic conflict illustration

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

  

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

     

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

     

然而,有一些策略可以显着帮助我们处理它们

     
      
  • 其中第一个是SelfTestingCode。测试有效地探测我们的代码,看看他们对代码语义的看法是否与代码的实际内容一致
  •   
  • 另一种有帮助的技术是更频繁地合并
  •   
     

通常人们会根据他们如何轻松地进行功能分支来尝试证明DVCS的合理性。但这忽略了语义冲突的问题   如果您的功能在几天内快速构建,那么您将遇到较少的语义冲突(如果不到一天,那么它实际上与CI相同)。但是,我们不经常看到这样的短特征分支。

我认为在拍摄分支和特征分支之间需要找到一个中间立场 如果您在相同的功能分支上有一组开发人员,那么合并通常是关键。

答案 1 :(得分:3)

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

答案 2 :(得分:0)

方案:存在方法foo()。此时开始两个分支。

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

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