更好、更简单的“语义冲突"示例? [英] Better, simpler example of 'semantic conflict'?

查看:32
本文介绍了更好、更简单的“语义冲突"示例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  • 文字
  • 语法
  • 语义

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

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

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

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

基本代码是:

int i = 0;整数赔率 = 0;而 (i <10){如果 ((i & 1) != 0){赔率 *= 10;赔率 += i;}//下一个++我;}断言(赔率 == 13579)

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

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

int i = 1;//L整数赔率 = 0;而 (i <10){如果 ((i & 1) != 0){赔率 *= 10;赔率 += i;}//下一个我 += 2;//L}断言(赔率 == 13579)

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

int i = 0;整数赔率 = 0;while (i <5)//R{赔率 *= 10;赔率 += 2 * i + 1;//R//下一个++我;}断言(赔率 == 13579)

这是合并或更新的结果,SVN 未检测到(这是 VCS 的正确行为),因此不是文本冲突.请注意,它会编译,因此不会出现语法冲突.

int i = 1;//L整数赔率 = 0;while (i <5)//R{赔率 *= 10;赔率 += 2 * i + 1;//R//下一个我 += 2;//L}断言(赔率 == 13579)

assert 失败,因为 odds 是 37.

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

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

解决方案

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

<块引用>

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

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

<块引用>

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

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

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

<小时>

话虽如此,这是最简单的例子,如 .测试有效地探查我们的代码,看看他们对代码语义的看法是否与代码实际执行的一致

  • 另一种有用的技术是更频繁地合并
  • 人们经常试图根据 DVCS 如何使特征分支变得容易来证明其合理性.但这忽略了语义冲突的问题.
    如果您的功能在几天内构建得很快,那么您遇到的语义冲突就会更少(如果不到一天,那么它实际上与 CI 相同).然而,我们并不经常看到如此短的特征分支.

    我认为需要在临时分支和功能分支之间找到一个中间立场.
    如果您有一组开发人员在相同功能分支,经常合并是关键.

    I like to distinguish three different types of conflict from a version control system (VCS):

    • textual
    • syntactic
    • semantic

    A textual conflict is one that is detected by the merge or update process. This is flagged by the system. A commit of the result is not permitted by the VCS until the conflict is resolved.

    A syntactic conflict is not flagged by the VCS, but the result will not compile. Therefore this should also be picked up by even a slightly careful programmer. (A simple example might be a variable rename by Left and some added lines using that variable by Right. The merge will probably have an unresolved symbol. Alternatively, this might introduce a semantic conflict by variable hiding.)

    Finally, a semantic conflict is not flagged by the VCS, the result compiles, but the code may have problems running. In mild cases, incorrect results are produced. In severe cases, a crash could be introduced. Even these should be detected before commit by a very careful programmer, through either code review or unit testing.

    My example of a semantic conflict uses SVN (Subversion) and C++, but those choices are not really relevant to the essence of the question.

    The base code is:

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

    The Left (L) and Right (R) changes are as follows.

    Left's 'optimisation' (changing the values the loop variable takes):

    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's 'optimisation' (changing how the loop variable is used):

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

    This is the result of a merge or update, and is not detected by SVN (which is correct behaviour for the VCS), so it is not a textual conflict. Note that it compiles, so it is not a syntactic conflict.

    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)
    

    The assert fails because odds is 37.

    So my question is as follows. Is there a simpler example than this? Is there a simple example where the compiled executable has a new crash?

    As a secondary question, are there cases of this that you have encountered in real code? Again, simple examples are especially welcome.

    解决方案

    It is not obvious to come up with simple relevant examples, and this comment sum up best why:

    If the changes are close by, then trivial resolutions are more likely to be correct (because those that are incorrect are more likely to touch the same parts of the code and thus result in non-trivial conflicts), and in those few cases where they aren’t, the problem will manifest itself relatively quickly and probably in an obvious way.

    [Which is basically what your example illustrates]

    But detecting semantic conflicts introduced by merges between changes in widely separated areas of the code is likely to require holding more of the program in your head than most programmers can – or in projects the size of the kernel, than any programmer can.
    So even if you did review those 3-way diffs manually, it would be a comparatively useless exercise: the effort would be far disproportionate with the gain in confidence.

    In fact, I would argue that merging is a red herring:
    this sort of semantic clash between disparate but interdependent parts of the code is inevitable the moment they can evolve separately.
    How this concurrent development process is organized – DVCS; CVCS; tarballs and patches; everyone edits the same files on a network share – is of no consequence at all to that fact.
    Merging doesn’t cause semantic clashes, programming causes semantic clashes.

    In other words, the real case of semantic conflicts I have encountered in real code after a merge were not simple, but rather quite complex.


    That being said, the simplest example, as illustrated by Martin Fowler in his article Feature Branch is a method rename:

    The problem I worry more about is a semantic conflict.
    A simple example of this is that if Professor Plum changes the name of a method that Reverend Green's code calls. Refactoring tools allow you to rename a method safely, but only on your code base.
    So if G1-6 contain new code that calls foo, Professor Plum can't tell in his code base as he doesn't have it. You only find out on the big merge.

    A function rename is a relatively obvious case of a semantic conflict.
    In practice they can be much more subtle.

    Tests are the key to discovering them, but the more code there is to merge the more likely you'll have conflicts and the harder it is to fix them.
    It's the risk of conflicts, particularly semantic conflicts, that make big merges scary.


    As Ole Lynge mentions in his answer (upvoted), Martin Fowler did write today (time of this edit) an post about "semantic conflict", including the following illustration:

    Again, this is based on function renaming, even though subtler case based on internal function refactoring are mentioned:

    The simplest example is that of renaming a function.
    Say I think that the method clcBl would be easier to work with if it were called calculateBill.

    So the first point here is that however powerful your tooling is, it will only protect you from textual conflicts.

    There are, however, a couple of strategies that can significantly help us deal with them

    • The first of these is SelfTestingCode. Tests are effectively probing our code to see if their view of the code's semantics are consistent with what the code actually does
    • The other technique that helps is to merge more often

    Often people try to justify DVCSs based on how they make feature branching easy. But that misses the issues of semantic conflicts.
    If your features are built quickly, within a couple of days, then you'll run into less semantic conflicts (and if less than a day, then it's in effect the same as CI). However we don't see such short feature branches very often.

    I think a middle ground needs to be found between shot-lived branches and feature-branches.
    And merging often is key if you have a group of developer on the same feature branch.

    这篇关于更好、更简单的“语义冲突"示例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆