Фиксация соглашений в команде
Соглашения — важная часть процесса разработки. Они призваны снижать затраты и упрощать жизнь сотрудников. Проблема в том, что часто соглашения, наоборот, только всё усложняют, поскольку они толком не зафиксированы и передаются исключительно из уст в уста, как старые сказки. В процессе передачи они обрастают новыми подробностями, а прежние подробности упускаются. В итоге у каждого участника команды в голове свой набор соглашений, которые он периодически забывает.
Ещё хуже, когда соглашения начинают-таки фиксировать, но делают это как попало — и вы получаете свалку непонятно связанных между собой текстов, половина которых неактуальна.
В этой статье я расскажу, как фиксировать соглашения правильно, чтобы они приносили вам пользу.
Потом в комментариях к какому-нибудь пулл-реквесту разгораются дебаты:, а какие соглашения актуальны, а точно ли им надо следовать, а не изобрести ли новое соглашение?
Как же добиться того, чтобы соглашения помогали, а не мешали? Их нужно фиксировать. И делать это так, чтобы:
было удобно пользоваться;
на следование этим соглашениям требовались минимальные трудозатраты;
легко было понять, какие соглашения актуальны;
легко было понять, почему приняты именно такие соглашения;
они были автоматизированы (в идеале).
Можно придумать множество классификаций соглашений. Я предпочитаю делить их по уровню абстракции. Вот самые частые из них:
соглашения на уровне кода;
соглашения на уровне архитектуры;
соглашения на уровне процессов.
Соглашения на разных уровнях абстракции приносят разную пользу, и их нужно по-разному фиксировать. Давайте рассмотрим каждый тип.
Соглашения на уровне кода
Эти соглашения направлены на то, чтобы код выглядел единообразно, понятно и читаемо. Вот несколько примеров таких соглашений:
Используем одинарные кавычки для строк вместо двойных.
Не обращаемся к ENV напрямую из кода, за исключением одного класса, где оборачиваем вызовы к ENV в методы.
Сервисные объекты имеют в конце названия слово Service и имеют один публичный метод call.
Такие соглашения преследуют одну прямую цель — снизить когнитивную нагрузку на читающего код разработчика, в том числе помочь ему быстрее ориентироваться в незнакомом коде. Как писал Мартин, код гораздо чаще читают, чем пишут, — разница больше чем в 10 раз. Как бы вы ни относились к Rails, в этом фреймворке на полную работает потрясающий принцип convention over configuration, что позволяет любому разработчику на Rails открыть чужой проект и сразу же довольно неплохо в нём ориентироваться.
Как фиксировать такие соглашения? Линтером. Если подходящего соглашения нет в правилах линтера, напишите собственный линт. Практически все линтеры позволяют это делать: вот пример для Go, а вот — для Ruby.
Фиксация подобных соглашений линтером даёт сразу три преимущества:
Разработчик не думает об этих правилах, линтер тут же подсветит все проблемы, а зачастую ещё и сам их поправит.
Если вы используете код-ревью, люди на нём не будут работать линтерами, а посмотрят на более важные вещи.
Разработчик увидит недочёт в самом начале разработки, а значит, не потратит время позже на возвращение в контекст — он сразу поправит ошибку. Соблюдать соглашение становится дёшево.
Небольшой бонус: написание правила для линтера — отличная практика для джуна. Пока он с ним разбирается, узнает больше о том, как парсится код, как строится AST, лучше поймёт язык.
Соглашения на уровне архитектуры
Это более высокоуровневый тип соглашений, направленный на то, чтобы сделать вашу архитектуру продуманной, согласованной и единообразной. Несколько примеров:
Используем язык Python для написания обычных сервисов и Elixir в нагруженных частях системы.
Бэкэнд отдаёт ошибки в таком-то формате.
Каждый сервис обязан отдавать метрики в prometheus, на эндпоинте /metrics, порт для отдачи метрик конфигурируется переменной окружения PROMETHEUS_PORT.
Такие соглашения, помимо снижения когнитивной нагрузки, решают ещё три задачи:
Снижают эксплуатационные издержки. Если сервисы единообразно запускаются, одинаково отдают логи, одинаково отдают метрики, то гораздо проще обслуживать сервис и разбираться с инцидентами.
Снижают издержки на проектирование. Разработчику не нужно каждый раз проектировать архитектуру с нуля — вы заранее подумали, и теперь нужно спроектировать только конкретную фичу/сервис, не беспокоясь о базовых вещах.
Снижают издержки на коммуникацию. Если ответ сервера или формат события в кафке описан заранее, разработчикам не нужно каждый раз договариваться, как строить взаимодействие, вместо этого можно просто сослаться на соглашение.
Такие соглашения более сложные, и я предпочитаю фиксировать их в два шага.
Шаг 1 — описываем
Architecture Decision Record (ADR) — инструмент фиксации подобных соглашений. Его прелесть в том, что он фиксирует не только соглашение, но и сопутствующую информацию: почему было принято такое соглашение; какие альтернативы обсуждали; когда соглашение последний раз пересматривалось; актуально ли соглашение сейчас.
Это позволяет новому члену команды понять причины принятых решений и не спрашивать об этом у людей вокруг.
ADR состоит из нескольких основных блоков:
Какую проблему решает соглашение.
Какие варианты решения проблемы рассматривались, их плюсы и минусы.
Какой вариант выбрали в итоге.
Могут быть и дополнительные блоки — например, расчёт стоимости внедрения выбранного решения.
ADR удобнее всего вести там, где видно историю изменений и обсуждений. Мой выбор — это Github и Notion, у каждого есть свои плюсы и минусы. Преимущество Github — в наличии «из коробки» инструмента для ревью и отслеживания истории версий. Notion привлекает удобством работы с базами данных в нём, тегами и тем, что с ним легко справятся и непрограммисты.
Желающим начать работать с ADR рекомендую взглянуть на репозиторий, в котором можно найти разные шаблоны ADR и примеры их использования.
Шаг 2 — автоматизируем
ADR сложнее автоматизировать, чем соглашения по коду: линтера для дизайна, кажется, пока не придумали (а жаль!). Тем не менее отчасти автоматизировать возможно и их, в зависимости от того, что это за соглашение.
Для соглашений по языкам, библиотекам, встраиванию сервисов в инфраструктуру и тому подобному создавайте и обновляйте шаблоны сервисов. Тогда при создании нового сервиса разработчик не пишет его с нуля, а копирует из шаблона и сразу получает настроенный Dockerfile, отдачу метрик и т. д.
Точно так же можно делать и генераторы классов внутри одного приложения. Например, если вы договорились о нескольких слоях (контроллер => форма => сервис-объект), можно сделать простую консольную команду, которая будет генерировать сразу все слои для новой фичи.
В случае если вы договорились о каких-то принципах, которые не автоматизировать подобным образом, вы можете организовать чек-листы, автоматически добавляемые в мердж-реквест или задачу в трекере, по ним разработчик может быстро пройтись перед передачей таски дальше.
Соглашения на уровне процессов разработки
По процессам в каждой компании есть множество соглашений, например:
— Описание того, как устроен наём в компанию.
Описание процесса выкатки релиза.
Наличие дизайн-ревью для больших задач.
Проведение синка два раза в неделю с обсуждением текучки.
До недавнего времени я не задумывался о фиксации этих соглашений, хотя они сильно влияют на успех компании. Фиксация этих соглашений, помимо бенефитов предыдущих типов, имеет ещё один — она позволяет рационализировать процессы, перенести их в видимую плоскость и подумать о целесообразности каждого из них.
Идею я подсмотрел у Виталия Шароватова на TeamleadConf Foundation 2022. К сожалению, видео его выступления пока недоступно, так что опишу кратко суть.
Виталий предложил инструмент, аналогичный ADR, — Process Decision Record (PDR). Единственное отличие — вместо архитектурных решений в нём описываются решения о процессах. Кроме того, он предложил в каждом PDR ставить «дату переосмысления» — когда вы возвращаетесь к документу, чтобы понять, решает ли он всё ещё ваши проблемы лучшим образом, через n месяцев после принятия (к слову, то же самое можно делать и с ADR).
Что касается автоматизации процессов, с ней всё сложно. Какие-то процессы вы можете автоматизировать, настроив worflow в jira, поставив напоминания для встреч или создав бота, который автоматически готовит презентацию итогов недели (я так делал, правда в чужой компании).
Но часто процессы толком не автоматизируешь, и главное — сделать так, чтобы следовать им было проще, чем не следовать. Тем не менее фиксация соглашений всё-таки будет полезна, даже если вашим процессам уже легко следовать, — формализация и рационализация позволят улучшать процессы.
И что в итоге?
В итоге — фиксация и последующая автоматизация соглашений выгодны: траты времени на разработку уменьшаются, приложения становятся более поддерживаемыми, а процессы — более разумными.
Может показаться, что всё это ненужная бюрократия, ведь «мы нормальные пацаны — и так всё порешаем». Но на самом деле соглашения значительно экономят время и берегут нервные клетки сотрудников. Конечно, не нужно возводить их в абсолют и отвергать любые вещи, идущие вразрез с договорённостями, — это могут быть признаки того, что соглашение устарело или изначально вы не подумали о каких-то его аспектах.
Если вы ещё не начинали фиксировать соглашения в своей команде, то я бы советовал вам идти от низкого уровня абстракции к высокому: вначале автоматизировать соглашения по коду, далее — по архитектуре и потом — по процессам. Фиксация соглашений — это привычка, которую нужно сформировать в команде, и гораздо проще начинать с менее абстрактных концепций.
Кроме того, есть и другие соглашения, не описанные мной в статье. Например, вы можете фиксировать соглашения по дизайну в виде библиотеки компонентов.
Для каждого нового типа соглашений проходите три этапа:
Придумайте, как его фиксировать.
Придумайте, как его автоматизировать.
По прошествии времени убедитесь, что соглашение экономит вам больше ресурсов, чем тратится на его поддержание.
Ну и последнее. В процессе фиксации соглашений может оказаться, что некоторые из них ничем не обоснованы, тратят время вашей команды и вообще вредят, но вы к ним уже привыкли. В этом случае вам придётся преодолеть барьер привычки в головах и убедить всех в том, что отказ от соглашений иногда не менее важен, чем их принятие. Но это уже совсем другая история.