Лучше, более простой пример "семантического конфликта"?

Мне нравится различать три разных типа конфликта от системы управления версиями (VCS):

  • текстовые
  • синтаксический
  • семантическая

Текстовый конфликт - это тот, который определяется процессом слияния или обновления. Это помечено системой. Конец результата не разрешен VCS до тех пор, пока конфликт не будет разрешен.

Синтаксический конфликт не помечен VCS, но результат не будет компилироваться. Поэтому это следует также поднять даже слегка осторожным программистом. (Простым примером может быть переименование переменной слева и некоторыми добавленными строками с использованием этой переменной справа. Слияние, вероятно, будет иметь неразрешенный символ. В качестве альтернативы это может привести к смысловому конфликту при смене переменной.)

Наконец, семантический конфликт не отмечен VCS, результат компилируется, но у кода могут быть проблемы с запуском. В мягких случаях производятся неправильные результаты. В тяжелых случаях может произойти авария. Даже они должны быть обнаружены перед фиксацией очень осторожным программистом посредством проверки кода или тестирования модулей.

Мой пример семантического конфликта использует SVN (Subversion) и С++, но эти варианты не имеют отношения к сути вопроса.

Базовый код:

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

Изменения 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)

Правая "оптимизация" (изменение способа использования переменной цикла):

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.

Итак, мой вопрос таков. Есть ли более простой пример? Есть ли простой пример, когда скомпилированный исполняемый файл имеет новый сбой?

В качестве второстепенного вопроса есть случаи, с которыми вы столкнулись в реальном коде? Опять же, простые примеры особенно приветствуются.

Ответ 1

Не обязательно придумывать простые соответствующие примеры, и этот комментарий лучше всего объясняет:

Если изменения близки, то тривиальные разрешения с большей вероятностью будут правильными (поскольку те, которые являются неправильными, более склонны касаться одних и тех же частей кода и, следовательно, приводят к нетривиальным конфликтам), и в тех немногих случаи, когда они возникают, проблема будет проявляться относительно быстро и, вероятно, очевидным образом.

[Что в основном иллюстрирует ваш пример]

Но обнаружение семантических конфликтов, возникающих в результате слияния между изменениями в широко разделенных областях кода, скорее всего, потребует большего количества программы в вашей голове, чем большинство программистов, - или в проектах размером с ядро, чем любой программист.
Поэтому, даже если вы просмотрите эти трехсторонние различия вручную, это будет сравнительно бесполезным упражнением: усилия будут намного несоразмерны с уверенностью в себе.

На самом деле, я бы сказал, что слияние - это красная селедка:
этот тип семантического столкновения между разрозненными, но взаимозависимыми частями кода неизбежен в тот момент, когда они могут развиваться отдельно.
Как организовывается этот параллельный процесс разработки - DVCS; ЦВК; архивы и патчи; все редактируют одни и те же файлы на сетевом ресурсе - не имеет никакого значения для этого факта.
Слияние не вызывает семантических столкновений, программирование вызывает семантические столкновения.

Иными словами, реальный случай семантических конфликтов, с которыми я столкнулся в реальном коде после слияния, был не простым, а довольно сложным.


Как говорится, самый простой пример, проиллюстрированный Мартин Фаулер в своей статье Feature Branch - это метод переименования:

Проблема, о которой я больше беспокоюсь, - это семантический конфликт.
Простым примером этого является то, что если профессор Плаум изменяет имя метода, который вызывает призыв преподобного Грина. Инструменты рефакторинга позволяют безопасно переименовывать метод, но только на базе кода.
Поэтому, если G1-6 содержит новый код, который вызывает foo, профессор Плам не может сказать в своей базе кода, поскольку у него его нет. Вы только узнаете о большом слиянии.

Переименование функции - относительно очевидный случай семантического конфликта.
На практике они могут быть гораздо более тонкими.

Тесты - это ключ к их обнаружению, но , чем больше кода для слияния, тем вероятнее, что у вас будут конфликты, и тем труднее их исправить.. Это риск конфликтов, особенно семантических конфликтов, которые делают большие слияния страшными.


Как Ole Lynge упоминает в свой ответ (upvoted), Мартин Фаулер написал сегодня (время этого редактирования) сообщение о "семантическом конфликте", включая следующую иллюстрацию:

semantic conflict illustration

Опять же, это основано на переименовании функций, хотя упоминается более тонкий случай, основанный на рефакторинге внутренней функции:

Самый простой пример - переименование функции.
Скажем, я думаю, что с помощью метода clcBl было бы легче работать, если бы оно называлось calculateBill.

Итак, первое, что нужно сделать, это то, что, насколько бы мощным ни был ваш инструмент, он защитит вас только от текстовых конфликтов.

Есть, однако, несколько стратегий, которые могут значительно помочь нам справиться с ними.

  • Первый из них SelfTestingCode. Тесты эффективно исследуют наш код, чтобы увидеть, согласуется ли их взгляд на семантику кода с тем, что на самом деле делает код
  • Другим способом, который помогает, является более частое слияние

Часто люди пытаются обосновать DVCS, основываясь на том, как они облегчают ветвление функций. Но это не учитывает вопросы семантических конфликтов.
Если ваши функции будут построены быстро, через пару дней, вы столкнетесь с меньшими семантическими конфликтами (а если меньше, чем за один день, то это по сути то же, что и CI). Однако мы не видим таких коротких ветвей функций очень часто.

Я думаю, что между разветвленными ветвями и ветвями-чертами должно быть найдено среднее место.
И слияние часто является ключевым, если у вас есть группа разработчиков в той же ветки функции.