Соглашения — важная часть процесса разработки. Они призваны снижать затраты и упрощать жизнь сотрудников. Проблема в том, что часто соглашения, наоборот, только всё усложняют, поскольку они толком не зафиксированы и передаются исключительно из уст в уста, как старые сказки. В процессе передачи они обрастают новыми подробностями, а прежние подробности упускаются. В итоге у каждого участника команды в голове свой набор соглашений, которые он периодически забывает.
Ещё хуже, когда соглашения начинают-таки фиксировать, но делают это как попало — и вы получаете свалку непонятно связанных между собой текстов, половина которых неактуальна.
В этой статье я расскажу, как фиксировать соглашения правильно, чтобы они приносили вам пользу.
Как же добиться того, чтобы соглашения помогали, а не мешали? Их нужно фиксировать. И делать это так, чтобы:
было удобно пользоваться;
на следование этим соглашениям требовались минимальные трудозатраты;
легко было понять, какие соглашения актуальны;
легко было понять, почему приняты именно такие соглашения;
они были автоматизированы (в идеале).
Можно придумать множество классификаций соглашений. Я предпочитаю делить их по уровню абстракции. Вот самые частые из них:
соглашения на уровне кода;
соглашения на уровне архитектуры;
соглашения на уровне процессов.
Соглашения на разных уровнях абстракции приносят разную пользу, и их нужно по-разному фиксировать. Давайте рассмотрим каждый тип.
Соглашения на уровне кода
Эти соглашения направлены на то, чтобы код выглядел единообразно, понятно и читаемо. Вот несколько примеров таких соглашений:
Используем одинарные кавычки для строк вместо двойных.
Не обращаемся к 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, поставив напоминания для встреч или создав бота, который автоматически готовит презентацию итогов недели (я так делал, правда в чужой компании).
Но часто процессы толком не автоматизируешь, и главное — сделать так, чтобы следовать им было проще, чем не следовать. Тем не менее фиксация соглашений всё-таки будет полезна, даже если вашим процессам уже легко следовать, — формализация и рационализация позволят улучшать процессы.
И что в итоге?
В итоге — фиксация и последующая автоматизация соглашений выгодны: траты времени на разработку уменьшаются, приложения становятся более поддерживаемыми, а процессы — более разумными.
Может показаться, что всё это ненужная бюрократия, ведь «мы нормальные пацаны — и так всё порешаем». Но на самом деле соглашения значительно экономят время и берегут нервные клетки сотрудников. Конечно, не нужно возводить их в абсолют и отвергать любые вещи, идущие вразрез с договорённостями, — это могут быть признаки того, что соглашение устарело или изначально вы не подумали о каких-то его аспектах.
Если вы ещё не начинали фиксировать соглашения в своей команде, то я бы советовал вам идти от низкого уровня абстракции к высокому: вначале автоматизировать соглашения по коду, далее — по архитектуре и потом — по процессам. Фиксация соглашений — это привычка, которую нужно сформировать в команде, и гораздо проще начинать с менее абстрактных концепций.
Кроме того, есть и другие соглашения, не описанные мной в статье. Например, вы можете фиксировать соглашения по дизайну в виде библиотеки компонентов.
Для каждого нового типа соглашений проходите три этапа:
Придумайте, как его фиксировать.
Придумайте, как его автоматизировать.
По прошествии времени убедитесь, что соглашение экономит вам больше ресурсов, чем тратится на его поддержание.
Ну и последнее. В процессе фиксации соглашений может оказаться, что некоторые из них ничем не обоснованы, тратят время вашей команды и вообще вредят, но вы к ним уже привыкли. В этом случае вам придётся преодолеть барьер привычки в головах и убедить всех в том, что отказ от соглашений иногда не менее важен, чем их принятие. Но это уже совсем другая история.
Комментарии (15)
Waversz
24.08.2022 19:40+1Не слишком ли усложнено? Написали требования, повесили на стеночку, шаг вправо, шаг влево - расстрел. Если возникает непонятка - на консультацию к старшим.
Хотя... в больших проектах без этого наверное никак.KeySmasher
25.08.2022 13:17В любом случае что то такое да возникнет, в крупных проектах уж точно. А к страшим на консультацию всегда надо!
andreishe
25.08.2022 02:30+1Как бы вы ни относились к Rails, в этом фреймворке на полную работает потрясающий принцип convention over configuration
Да ладно. Фреймворков много, а я - один. И конвеншены везде свои. Я за то, чтобы было понятно, что код делает с минимальной (идеально без) подготовкой. Обилие соглашенческой магии этому не способствует.
iliazeus
25.08.2022 10:44Думаю, имеются в виду разумные соглашения, которые исходят скорее из архитектуры или предметной области, чем из левой пятки фреймворка.
С Rails не знаком, но гугл говорит, что там соглашения вида "when you have a User model in your application then Rails assumes that it is defined in the file at app/models/user.rb", что достаточно логично, и не требует особой подготовки.
unkmas Автор
25.08.2022 11:00+2Да, всё так, я тут говорил как раз о подобных конвенциях - как минимум, ты знаешь всегда что где лежит, как именуется.
Ну и конвенции не отменяют читаемого кода, а дополняют его)
Yago
25.08.2022 15:40Делали без ADR, более упрощенно:
1) Выработали план, по которому принимаем соглашения на уровне стека
2) Выработали подход по плану. Назначался ведущий, который готовил и принимал последующие вопросы и пункты к обсуждению, согласовали формат обсуждений и оповещений об изменениях.
3) Раз в неделю собирались стеком и шли по плану.
4) В ходе обсуждений задавались конкретные вопросы и фиксировались результаты обсуждений в виде рекомендаций или обязательных правил. Фиксировались на странице вики в формате markdown, об изменениях оповещались участники процесса.
5) В параллеле с вопросами стека применили похожую схему к процессам в команде (git-flow, ревью, детализация, тестирование, практика релизов).
6) При дальнейших действиях в команде ее участник уже имел справочную информацию в виде документов, на которые можно опираться при принятии решений.
7) Попробовали нововведения в бою, настроили механизм отладки соглашений, запустили переодический пересмотр.
8) Автоматизировали лишь спустя какое-то время, и далеко не все. Потому что некоторые правила требовали достаточно весомых затрат на автоматизацию.
9) Запустили таким образом кросс-командные соглашения.
Это, конечно, все потребовало очень много времени на согласования и настройку продуктивной атмосферы для принятия решений, но в итоге получили прозрачные процессы. Если кратко резюмировать, то это дорого, и не везде применимо. Но стоит того.
lxsmkv
25.08.2022 16:04Интересно, конечно. С одной стороны у нас аджайл с его принципами
Люди и взаимодействие приоритетнее процессов и инструментов .
Сотрудничество с заказчиком важнее согласования условий контракта
Работающий продукт важнее исчерпывающей документации
...С другой стороны мы вводим формализацию и инструменты для ее автоматизации.
P.S.: Кстати, если смотреть на контрактную разработку то корпоративным заказчикам по факту важнее именно ценности справа, а не слева, хоть они и требуют чтобы их команды работали по аджайлу. Единственный принцип корпоративного аджайла это "прикрытие своей задницы важнее всего остального".
unkmas Автор
25.08.2022 18:37+1Так это не противопоставление. Это фраза не про то, что не должно быть процессов, она про то, что процессы надо адаптировать под свою команду, а не копировать жёстко практики. Люди важнее, но у нас же всё равно есть какой-то процесс, так? Этот процесс может быть осмысленным, а может быть и нет. Вот я и предлагаю осмыслять и фиксировать осмысление.
fo_otman
Есть идея создать инструмент для написания и поддержки ТЗ. Который будет частично перекрывать по функционалу то, что описано в статье. Будет включать в себя удобство как в Google Docs, версионирование как в Git и подсветку изменений как в Diffchecker. Эдакое ТЗ с удобным просмотром истории изменений. С меня бэкенд, но 80% работы там на фронте. Есть тут желающие контрибьютить свободное ПО?)
Houston
Сделать Google Docs в свободное от работы время? Ну да, ну да)
sciomenihilscire
так есть же markdown и git
fo_otman
Да, формат удобнее всего Markdown. Git хорошая вещь, но менеджеры с ним не работают, это слишком специализированный инструмент. На фронте будет editor.js, сохраняться в базу с комментарием о причинах правки, автором, датой правки, автоинкрементации версии. Затем будет страница типа /?from=1.23&to=1.24, которая в удобном виде подсветит разницу между 2мя версиями, как в DiffChecker. В публичном доступе ТЗ будет выглядеть, как любая типичная документация. А под капотом - крутые фичи, облегчающие жизнь компании. Больше никаких неактуальных ТЗ, никаких правок мимо ТЗ, никаких забытых договоренностей, никаких белых пятен в логике.
TonyKentnarEarth
notion, confluence, wiki gitlab'а/bitbucket'а/github'а - тысячи их))
fo_otman
У них у всех нет возможности указывать причину правки, опционально инкрементировать версию, нет сравнения 2х любых версий.
asolokha
Плагин к obsidian допишите, git там уже есть. Хотя вроде уже и встречал плагин версионки так что даже не с пустого места начнете