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

Rhu*_*arb 11 svn version-control conflict

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

  • 文本
  • 句法
  • 语义

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

一个句法冲突不是由VCS标记,但结果将无法编译.因此,即使是一个稍微小心的程序员也应该这样做.(一个简单的例子可能是左边的变量重命名和一些使用右边的变量添加的行.合并可能有一个未解析的符号.或者,这可能会通过变量隐藏引入语义冲突.)

最后,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)
Run Code Online (Sandbox Code Playgroud)

Left(L)和Right(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)
Run Code Online (Sandbox Code Playgroud)

正确的'优化'(改变循环变量的使用方式):

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

这是合并或更新的结果,并且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)
Run Code Online (Sandbox Code Playgroud)

assert,因为失败odds是37.

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

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

Von*_*onC 8

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

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

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

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

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

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


话虽这么说,最简单的例子,如Martin Fowler在他的文章Feature Branch中所说的是一个方法重命名:

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

函数重命名是语义冲突的一个相对明显的例子.
在实践中,它们可以更加微妙.

测试是发现它们的关键,但合并的代码越多,就越有可能发生冲突,修复冲突的难度就越大.
冲突的风险,特别是语义冲突,使大合并变得可怕.


正如Ole Lynge他的回答(upvoted)中提到的,Martin Fowler今天(编辑的时间)写了一篇关于"语义冲突"的帖子,包括下面的插图:

语义冲突图

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

最简单的例子是重命名函数.
假设我认为clcBl如果调用它,该方法将更容易使用calculateBill.

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

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

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

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

我认为需要在镜头分支和特征分支之间找到中间立场.
而且,如果你对一组开发人员的合并往往是关键的相同特性分支.