我想从版本控制系统(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)更改如下。

Left的“优化”(更改循环变量采用的值):

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)


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

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。

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

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

最佳答案

提出简单的相关示例并不明显,而此注释最能概括以下原因:


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


[基本上是您的示例所说明的内容]


但是,要检测由代码分隔的不同区域中的更改之间的合并所引起的语义冲突,可能需要比大多数程序员拥有更多的程序,或者在内核大小的项目中拥有比任何程序员都更多的能力。
因此,即使您确实手动查看了这些三向差异,这也将是一个相对无用的练习:付出的努力与信心的增长将远远不成比例。

实际上,我认为合并是一个红鲱鱼:
在代码的不同但相互依赖的部分之间发生这种语义冲突是不可避免的,因为它们可以分开发展。
并发开发过程的组织方式– 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 - 更好,更简单的“语义冲突”示例?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/2514502/

10-13 09:32
查看更多