Особенностьотрасль

С появлением распределенных систем контроля версий (DVCS), таких как Git и Mercurial, я все чаще вижу дискуссии о правильном использовании ветвления и слияния и о том, как это вписывается в идею непрерывной интеграции (CI).

В этом вопросе есть некоторая неясность, особенно когда речь идет о ветвлениях функций и их соответствии идеям CI.



Простая (изолированная) ветвь функций
Основная идея функциональной ветки — создавать новую ветку, когда вы начинаете работать над каким-то функционалом.

В DVCS вы делаете это в своем собственном репозитории, но те же принципы работают и в централизованных VCS. Я проиллюстрирую свои мысли следующим набором диаграмм.

В них основная линия развития (ствол) отмечена синим цветом, а зелёным и фиолетовым отмечены два застройщика (Преподобный Грин и Профессор Плам).



ОсобенностьОтрасль

Я использую выделенные цветные прямоугольники для обозначения локальных коммитов в ветках.

Стрелки между ветвями указывают на слияния, оранжевые прямоугольники выделяют сами слияния.

В этом примере есть обновления основной ветки, скажем пара исправленных ошибок.

Когда это происходит, наши разработчики объединяют их в свои локальные филиалы.

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

Чтобы убедиться, что код работает, они могут запускать сборки и тесты в своих ветках.

Для целей этой статьи предположим, что вместе с каждым коммитом и слиянием запускаются автоматические сборки и тесты для той ветки, в которой они были сделаны.

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

Они могут объединять изменения из основной ветки в удобном для них темпе и быть уверенными, что это не помешает разрабатываемому функционалу.

Более того, это дает команде возможность выбирать, какие новые разработки включить в релиз, а что оставить на потом.

Если преподобный Грин опоздает, мы сможем предоставить только версию с изменениями профессора Плама.

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

В этом случае мы просто попросим профессора не сливать его изменения в основную ветку, пока мы не будем готовы выпустить его функционал.

Такой подход дает нам возможность быть избирательными; перед каждым выпуском команда решает, какую функциональность объединить.

Каким бы привлекательным ни было это изображение, при таком подходе могут возникнуть некоторые проблемы.



ОсобенностьОтрасль

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

В нашем примере Профессор Плам легко обновляет основную строку своими изменениями; слияния нет, потому что все изменения основной ветки он уже получил в свою ветку (и завершил сборку).

Однако у преподобного Грина не все так просто, он должен объединить все свои изменения (G1-6) с изменениями профессора Плама (P1-5).

(В этом примере многие пользователи DVCS могут почувствовать, что мне не хватает многих деталей в таком простом и даже упрощенном объяснении ветвления признаков.

Более сложную диаграмму я объясню позже.

) Я сделал этот прямоугольник слияния огромным, потому что это опасное слияние.

Возможно, все пройдет гладко, вполне вероятно, что разработчики работали над разными частями кода без взаимодействий, и тогда слияние пройдет гладко.

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

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

Современные DVCS хорошо справляются с такими задачами, иногда даже кажется, что с помощью волшебства.

Git имеет репутацию хорошо справляющегося со сложными конфликтами.

Настолько хорошо, что мы даже оставим этот вопрос за рамками данной статьи.

Проблема, которая нас беспокоит больше всего, — это смысловые конфликты.

Самый простой пример: профессор Плам меняет имя метода, который преподобный Грин вызывает в своем коде.

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

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

Осознание того, где зарыта собака, придет к нам только в большом слиянии.

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

На практике они могут быть гораздо более скрытными.

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

Риск конфликтов вообще и смысловых в частности пугает крупные слияния.

Следствием страха перед большими слияниями является нежелание проводить рефакторинг.

Поддержание чистоты кода требует постоянных усилий, и чтобы добиться успеха, каждый должен убирать мусор, когда он его видит. Однако такой рефакторинг в функциональной ветке проблематичен, поскольку он делает Большое Страшное Слияние еще больше и страшнее.

В результате разработчики чертовски боятся рефакторинга, и код становится полным чудовищ.

В приведенной выше проблеме я вижу основную причину, по которой ветвление функций — плохая идея.

В тот момент, когда команда боится рефакторинга для поддержания работоспособности кода, она находится в долгом погружении без шансов на элегантный выход.

Непрерывная интеграция
Именно эти проблемы должна решить непрерывная интеграция.

С CI моя диаграмма будет выглядеть так.



ОсобенностьОтрасль

Здесь гораздо больше слияний, но слияние — одна из тех вещей, которые лучше делать понемногу, а не редко и тоннами.

В результате, если профессор Плам изменит часть кода, от которого зависит преподобный Грин, наш зеленый коллега разберется в этом гораздо раньше, на вехах P1-2. На этом этапе ему нужно изменить G1-2, чтобы обработать эти изменения, вместо G1-6 (как было в предыдущем примере).

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

В этом сценарии потенциальный конфликт возникнет, когда профессор Плам объединит G1 и поймет, что преподобный Грин использует библиотеки профессора.

Тогда профессор Плам сможет найти преподобного Грина, и вместе они смогут обсудить, как взаимодействуют их функциональные возможности.

Возможно, функционал Профессора Пума требует некоторых изменений, не вписывающихся в функционал Преподобного Грина.

Вместе они смогут принимать гораздо лучшие проектные решения, которые не будут мешать их работе.

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

Коммуникация — один из ключевых факторов разработки программного обеспечения, и одно из основных свойств CI — ее облегчение.

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

Это непрерывная сборка, и это хорошо, но здесь нет интеграция , поэтому это не CI.

«Грязная» интеграция
Ранее в скобках я сказал, что существуют и другие способы ветвления функций.

Допустим, профессор Плам и преподобный Грин вместе заваривают ароматный зеленый чай в начале итерации и обсуждают свои задачи.

Они обнаруживают, что среди задач есть взаимодействующие части, и решают интегрироваться друг с другом вот так:

ОсобенностьОтрасль

При таком подходе они сливаются в основную строку в конце, как и в первом примере, но также часто сливаются между собой, чтобы избежать Big Scary Merge. Идея состоит в том, что основным преимуществом ветвления функций является изоляция.

Когда вы изолируете свои ветки, существует риск возникновения ужасного конфликта, выходящего из-под вашего контроля.

Тогда изоляция — это иллюзия, которая рано или поздно болезненно разрушится.

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

Интеграция функциональных ветвей, которую я назову «беспорядочной интеграцией» (PI), не включает в себя основную линию и даже не нуждается в ней.

Я думаю, что эта разница очень важна.

Я рассматриваю CI главным образом как средство создания кандидата на выпуск при каждом коммите.

Задача CI-системы и процесса развертывания — опровергнуть готовность к производству текущего релиз-кандидата.

Эта модель нуждается в какой-то основной линии развития, отражающей текущее состояние полной картины.

-- Дэйв Фарли



Беспорядочная интеграция против непрерывной интеграции
И все же, если ПИ отличается от CI, то в каких случаях PI лучше CI? При использовании CI вы теряете возможность использовать контроль версий для выборочного подхода к изменениям.

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

При CI основная линия всегда должна быть работоспособной, и теоретически (а часто и на практике) вы можете освобождать ее после каждого коммита.

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

В таких случаях ПИ может предложить что-то среднее.

Это позволяет преподобному Грину выбирать, когда принять изменения профессора Плама.

Если профессор Плам внесет некоторые изменения в основной API в P2, преподобный Грин может импортировать P1-2, но оставить остальное до тех пор, пока профессор Плам не завершит свою работу и не объединит его с основной веткой.

Однако в целом я не думаю, что функция выборки для выпуска с использованием VCS — хорошая идея.

Ветвление функций — это модульная архитектура для нищих, вместо построения системы с возможностью легкой замены функциональности во время выполнения/развертывания люди привязывают себя к системе управления версиями для этого механизма посредством ручного слияния.

Дэн Бодарт

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

Для этого есть два полезных метода.

FeatureToggles И ФилиалПоАбстракция .

Они требуют от вас больше думать о том, что и как модулировать и как контролировать эти параметры, но мы обнаружили, что результат намного лучше, чем тот, который вы получаете, если полагаетесь на VCS. Что меня больше всего беспокоит в ИП, так это его уязвимость к коммуникативным способностям внутри команды.

При CI точка связи служит основной линией.

Даже если профессор Плам и преподобный Грин никогда не разговаривали, они обнаружат зарождающийся конфликт в тот же день, когда он возникнет. Что касается PI, им придется заметить, что они работают над совместимым кодом.

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

PI возник из открытого исходного кода, и, предположительно, менее интенсивный темп проекта с открытым исходным кодом может быть фактором для этого.

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

Это позволяет работать над функциональностью с приоритетами.

С открытым исходным кодом люди часто тратят час здесь и пару дней там.

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

В такой ситуации избирательный подход может оказаться более важным.

Важно понимать, что используемые вами инструменты не зависят от выбранной вами стратегии.

Несмотря на то, что многие люди ассоциируют DVCS с ветвлением функций, их также можно использовать с CI. Все, что вам нужно сделать, это пометить одну из ветвей как главную линию.

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

Фактически, в хорошо дисциплинированной команде я бы предпочел использовать DVCS для проекта CI, чем централизованную VCS. Я бы беспокоился, что в менее дисциплинированной команде использование DVCS подтолкнет людей к долгоживущим веткам, в то время как централизованная VCS и возросшая сложность ветвления заставят их чаще переходить к основной ветке.

P.S. От переводчика побудило меня изучить вопросы, связанные с подходами к использованию VCS Эта статья , благодаря чему я начал искать более подробные описания «правильного» использования бранчей и наткнулся на приведенный выше переведенный текст. Хоть я и не претендую на качество перевода, я просто хочу залезть в ленту разработчиков и дать им повод задуматься об обратном подходу, принятому в open source (форк).

Не бейте палками, а критикуйте конструктивно, я первый раз это делаю :-) .

Теги: #непрерывная интеграция #ci #vcs #ветвление #конфликт слияния #git

Вместе с данным постом часто просматривают: