Когда твой код стал общим: история от дебюта до эндшпиля
«Отстаньте от меня, пожалуйста, я — творец! Дайте мне творить!», — программист Геннадий уже третий раз за вечер проговаривает эту мантру у себя в голове. Тем не менее пока что он не написал ни одной строчки кода, потому что в библиотеку, которую пытается развивать, прилетел еще один пулл-реквест. А, согласно политике компании, ревью кода должно проходить с минимальными задержками. Теперь Геннадий думает, как поступить: не глядя принять изменения, так же не глядя их отклонить или все-таки потратить драгоценное время, чтобы разобраться в их сути. Ведь кто, кроме него? Он этот код написал, он за ним и будет следить. А все изменения возможны только через его персональное согласие, ведь это Библиотека Судного Дня.
Тем временем буквально за стенкой команда с названием «Кедровые бобры» перераспределяет реквесты между собой, чтобы нагрузка по их просмотру ложилась более менее равномерно. Да, они не занимаются Библиотекой Судного Дня, но решают другие задачи, которые требуют быстрого изменения кода и более шустрых процессов.
Универсального решения для всех случаев нет: кому-то важны налаженность и быстрота процессов, где-то вполне может оказаться необходимой твердая рука и тотальный контроль. Более того, на разных этапах разработки одного и того же продукта могут понадобиться разные подходы, заменяющие друг друга. У каждого из них есть свои плюсы и недостатки, и, исходя из них, мы пришли туда, где мы сейчас.
Так какой путь мы проделали в Wrike?
Из каких вариантов мы выбирали свой способ владения кодом
Строго персональный. Такой мы даже не рассматривали. Вот если Геннадий запретит создавать пулл-реквесты в свою библиотеку, а все изменения будет делать лично, то получится строго персональный подход. Наверняка Геннадий так и начинал.
Из очевидных минусов подхода — это просто тоталитаризм в мире разработки. Геннадий — без преувеличения единственный человек на Земле, который досконально знает код, имеет (или нет) планы на его развитие и может его изменять. Из-за поворота уже выехал тот самый автобус, который «бас-фактор». Если Геннадий подхватит простуду, то, скорее всего, проект сляжет вместе с ним. Другим разработчиком придется делать форки, их станет очень много, и наступит полный хаос.
У такого подхода есть один плюс — совершенно консолидированный подход к развитию. Один человек принимает все решения по архитектуре, code style и лично решает любой вопрос. Никаких накладных расходов на коммуникации.
Условно персональный. Это как раз то, чем Геннадий заниматься не хочет: смотреть все МР, давать возможность изменять код своей библиотеки другим людям, но иметь полный контроль над изменениями и обладать правом вето. Плюсы и минусы такие же, как в прошлом пункте, но теперь они немного сглажены возможностью отправлять сторонним разработчикам пулл-реквест прямо в репозиторий, а не составлять ТЗ на реализацию каких-то фичей.
Коллективный, как у «Кедровых бобров». В этом случае ответственность за код несет вся команда, и ее участники сами решают, кто какой реквест будет смотреть.
Из плюсов можно отметить большую скорость просмотра ревью, распределение экспертизы между участниками команды и снижение bus factor. Конечно, есть и недостатки. В обсуждениях в интернете многие упоминают отсутствие ответственности, если она «размазана» между несколькими людьми. Но это зависит от структуры команды и культуры разработчиков: за команду может отвечать Senior Developer или тимлид, тогда именно он будет входной точкой для вопросов. А МР и написание новых фичей можно разделять по уровню подготовки разработчика. Ведь было бы неправильным давать рефакторить код новичку, который только начинает разбираться в архитектуре приложения.
Как мы перешли к коллективному подходу владения кодом с человеческим лицом
В Wrike мы используем коллективный подход к владению кода, в котором главным ответственным является лид команды. Этот человек обладает наибольшей экспертизой в коде, знает, кто из разработчиков компетентен в ревью конкретной сложности, и несет всю ответственность за качество кода команды.
Но путь к технической реализации этого решения был не самым простым. Да, на словах все звучит довольно легко: вот фича, вот команда. Команда знает, за что отвечает, а значит будет следить за этим.
Такие соглашения могут работать как устный контракт, если количество команд меньше, чем пальцев на руке. А в нашем случае это больше тридцати команд и миллионы строк кода. Причем часто границы фичи нельзя обозначить репозиторием: встречаются довольно тесные интеграции одних фич в другие.
Панель фильтров справа — одинаковая для всех вьюшек. Это фича команды «А». При этом все вьюшки — это фичи других трех команд
Самый очевидный пример — фильтры. Они выглядят и ведут себя одинаково во всех возможных вью, при этом сами вью могут отличаться по функциональности. Это значит, что вью принадлежит одной команде, а единая панель фильтров — другой. И так десятки репозиториев, тысячи файлов разного кода. К кому идти с ревью, если нужно сделать изменения в конкретном файле?
Сначала мы пытались решить эту проблему простым JSON-файлом, который лежал в корне репозитория. Там были указаны описание функциональности и имена ответственных людей. К ним можно было обратиться, чтобы получить ревью по своему пулл-реквесту.
Это чем-то похоже на условную персональную модель владения кодом. Единственное исключение — в качестве ответственного указывается не один человек, а два или три. Но этот подход никогда не устраивал нас: люди переходили в другие команды, заболевали, уходили в отпуск, увольнялись, и каждый раз приходилось сначала искать того, кто замещает указанного владельца, а потом говорить, чтобы владельцы вручную поменяли имя и запушили изменения.
Позже перешли от конкретных людей к указанию команд. Правда, все в том же JSON-файле. Ощутимо лучше не стало, потому что теперь нужно было найти участников команды, которым можно отправить код на ревью. А у нас сотни (немного лукавлю, почти 70) фронтенд-разработчиков, и найти всех участников в то время было нелегко. Система владения уже стала коллективной, но искать нужных людей порой было не проще, чем искать заместителя владельца из прошлой версии. Плюс проблему с кодом, в котором могли пересекаться несколько фичей, все еще не удалось решить.
Поэтому критически важным было решить два вопроса: как закрепить за определенной командой отдельные фичи внутри репозитория другой команды и как сделать простой и доступной информацию по всем командам, которые могут владеть кодом.
Почему готовые инструменты нам не подошли. На рынке существуют инструменты для назначения людей на ревью и связи конкретных персоналий с кодом. При их использовании не нужно прибегать к созданию файлов с именами людей, к которым необходимо бежать в случае ревью, багов, сложных рефакторингов.
У Azure DevOps Services есть функциональность — Automatically include code reviewer. Название говорит само за себя, и один мой бывший коллега говорит, что у них в компании этот инструмент используется и весьма успешно. Мы не работаем с Azure, так что было бы здорово услышать от читателей, как там дела с авторевьюером.
Мы пользуемся GitLab, поэтому было бы логичным шагом посмотреть в сторону GitLab Code Owners. Но принцип работы этого инструмента не подходил нам: функциональность GitLab — это связка путей в репозитории (файлов и папок) и людей через их аккаунты в GitLab. Эта связка записывается в специальный файл — codeowners.md. Нам нужна была связка пути и фичи. Причем фичи у нас содержатся в специальном словаре, где ставятся в соответствие команде. Это позволяет размечать сложные фичи, которые могут существовать не в одном репозитории, разрабатываться несколькими командами, и, опять же, не привязываться к конкретным именам. Плюс у нас были планы на использование этой информации для того, чтобы создать удобный каталог команд, связанных с ними фичами и всеми участниками команды.
В итоге мы решили создать свою систему контроля владения кодом. Реализация первой версии нашей системы была основана на возможностях Dart SDK, потому что сначала она была запущена для репозиториев фронтенд департамента и только для Dart-файлов. Мы использовали собственные мета-теги (благо это поддержано на уровне языка), затем статическим анализатором пробегали по всем исходным файлам и составляли что-то наподобие таблицы: Файл/Фича — Команда-оунер. Размечать можно как отдельные файлы, так и целые пути с несколькими папками.
Спустя некоторое время разметка фичами у нас стала доступна для кода на Dart, JS и Java, а это вся кодовая база: и фронтенд, и бэкенд. Для того, чтобы получить информацию о владельцах, используется статический анализатор. Но, конечно, уже не тот, что был в первой версии и работал только с Dart-кодом. Например для Java-файлов используется библиотека javaparser. Эти анализаторы запускаются по расписанию и собирают всю актуальную информацию в одном реестре.
Помимо привязки определенного кода к командам-оунерам, мы построили интеграцию с сервисом сбора ошибок в продакшне и разместили всю полезную информацию о командах и фичах на внутреннем ресурсе. Теперь любой сотрудник может посмотреть, к кому бежать, если вдруг возникли вопросы в определенной вьюшке. А еще мы сделали автоматическим создание задачек на ответственных в случае каких-то глобальных изменений, как, например, переезд на новую версию Dart или Angular.
Кликнув по команде, вы сможете увидеть все фичи, всех участников команды, какие фичи сугубо технические, а какие продуктовые
В итоге мы получили не просто довольно гибкую систему связывания фичей с командами, но и полноценную инфраструктуру, которая помогает, отталкиваясь от кода, найти связанную с ним фичу, команду со всеми участниками, продакт-оунера фичи, баг-репорты.
Среди недостатков можно отметить необходимость внимательно следить за разметкой фичей при рефакторингах и переносах кода из одного места в другое и потребность в дополнительных мощностях, чтобы собрать всю информацию о разметке.
А как вы решаете проблему владения кода у себя? И есть ли какие-то смежные с ним проблемы и, главное, их решение?