Мы работаем над проектами, но мы используем много кода между проектами и имеем много библиотек, которые содержат наш общий код. По мере того, как мы реализуем новые проекты, мы находим больше способов выделить общий код и поместить его в библиотеки. Библиотеки зависят друг от друга, а проекты зависят от библиотек. Каждый проект и все библиотеки, используемые в этом проекте, должны использовать одну и ту же версию всех библиотек, на которые они ссылаются. Если мы выпустим часть программного обеспечения, нам придется исправлять ошибки и, возможно, добавлять новые функции в течение многих лет, а иногда и десятилетий. У нас около дюжины библиотек, изменения часто затрагивают более двух, и несколько команд работают над несколькими проектами параллельно, внося параллельные изменения во все эти библиотеки.
Недавно мы перешли на git и создали репозитории для каждой библиотеки и каждого проекта. Мы используем stash в качестве общего репозитория, делаем новые вещи в функциональных ветках, затем делаем запросы на извлечение и объединяем их только после просмотра.
Многие из проблем, с которыми нам приходится сталкиваться в проектах, требуют от нас внесения изменений в несколько библиотек и специфический код проекта. Они часто включают изменения интерфейсов библиотеки, некоторые из которых несовместимы. (Если вам кажется, что это звучит странно: мы взаимодействуем с оборудованием и скрываем определенное оборудование за общими интерфейсами. Почти каждый раз, когда мы интегрируем оборудование другого производителя, мы сталкиваемся со случаями, которые наши текущие интерфейсы не ожидали, и поэтому должны их усовершенствовать.) Например, представьте проект с P1
использованием библиотек L1
, L2
и L3
. L1
также использует L2
и L3
, и L2
использует L3
также. Граф зависимостей выглядит так:
<-------L1<--+
P1 <----+ ^ |
<-+ | | |
| +--L2 |
| ^ |
| | |
+-----L3---+
Теперь представьте, что функция для этого проекта требует изменений P1
и L3
изменения интерфейса L3
. Теперь добавьте проекты P2
и P3
в смесь, которая также ссылается на эти библиотеки. Мы не можем позволить себе переключить их на новый интерфейс, выполнить все тесты и развернуть новое программное обеспечение. Так какая альтернатива?
- реализовать новый интерфейс в
L3
- сделать запрос на получение
L3
и дождаться обзора - объединить изменения
- создать новый выпуск
L3
- начните работать над этой функцией
P1
, сделав ссылку наL3
ее новую версию, затем внедрите эту функцию вP1
ветку функций - сделайте запрос на удаление, проверьте это и объедините
(Я только что заметил, что забыл переключиться L1
и L2
на новый релиз. И я даже не знаю, куда это вставить, потому что это должно было бы быть сделано параллельно с P1
...)
Это утомительный, подверженный ошибкам и очень длительный процесс для реализации этой функции, он требует независимых проверок (что делает проверку намного сложнее), вообще не масштабируется и, вероятно, выведет нас из бизнеса, потому что мы настолько увязнуть в процессе, что мы ничего не сделаем.
Но как мы используем ветвление и тегирование для создания процесса, который позволяет нам реализовывать новые функции в новых проектах без лишних затрат?