代码之家  ›  专栏  ›  技术社区  ›  Rhubbarb

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

  •  10
  • Rhubbarb  · 技术社区  · 14 年前

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

    • 文本的
    • 句法的
    • 语义的

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

    句法的 VCS没有标记冲突,但不会编译结果。因此,即使是一个稍微谨慎的程序员也应该注意到这一点。(一个简单的例子可能是变量重命名方式 一些使用该变量的附加行 赖特 . 合并可能有一个未解析的符号。或者,这可能会引入 语义的 变量隐藏冲突。)

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

    我的一个语义冲突示例使用的是反汇编和反汇编,但是这些选择与问题的本质并不真正相关。

    基本代码是:

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

    左派( L )和权利( R )变化如下。

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

    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)
    

    赖特 's'优化(更改循环变量的使用方式):

    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。

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

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

    2 回复  |  直到 13 年前
        1
  •  8
  •   Community CDub    7 年前

    不明显能想出简单的办法 相关的 举例来说,这条评论总结了最好的原因:

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

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

    但是,要检测由代码的广泛分离区域中的更改之间的合并所引入的语义冲突,很可能需要比大多数程序员更多的头脑中的程序,或者在内核大小的项目中,比任何程序员都要多。
    因此,即使你真的手动查看了这些3向差异,这也将是一个相对无用的练习:所做的努力将与信心的增加不成比例。

    事实上,我认为合并是一个危险因素:
    当代码的不同但相互依赖的部分可以分开发展时,它们之间的这种语义冲突是不可避免的。
    这个并发开发过程是如何组织的dvcs;cvcs;tarballs和patches;每个人都在网络共享上编辑相同的文件,这一点都不重要。
    合并不会导致语义冲突,编程会导致语义冲突。

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


    也就是说,最简单的例子,如 Martin Fowler in his article Feature Branch 是方法重命名:

    我更担心的问题是语义冲突。
    一个简单的例子是,如果Plum教授更改了Green牧师代码调用的方法的名称。重构工具允许您安全地重命名方法,但只能在代码基础上重命名。
    因此,如果g1-6包含调用foo的新代码,那么plum教授在他的代码库中无法分辨,因为他没有。你只知道大合并。

    函数重命名是语义冲突的一个相对明显的例子。
    实际上,它们可能更微妙。

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


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

    semantic conflict illustration

    同样,这是基于函数重命名的,尽管基于 内部的 提到了函数重构:

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

    因此,这里的第一点是,无论您的工具多么强大,它只会保护您不受文本冲突的影响。

    然而,有两种策略可以显著地帮助我们处理这些问题

    • 首先是 SelfTestingCode . 测试正在有效地探测我们的代码,以查看它们对代码语义的看法是否与代码实际所做的一致
    • 另一个有帮助的方法是更频繁地合并

    通常,人们会根据dvcss如何使功能分支变得容易来证明dvcss的合理性。但这忽略了语义冲突的问题。
    如果您的功能在几天内快速构建,那么您将遇到更少的语义冲突(如果少于一天,那么它实际上与ci相同)。但是我们很少看到这样短的特征分支。

    我认为应该在被射杀的树枝和特写树枝之间找到一个中间地带。
    如果您有一组开发人员在 相同的 特征分支 .

        2
  •  3
  •   Ole Lynge    13 年前

    看看martin fowler在这篇文章中的例子: http://martinfowler.com/bliki/SemanticConflict.html