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

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

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

Потом в комментариях к какому-нибудь пулл-реквесту разгораются дебаты: а какие соглашения актуальны, а точно ли им надо следовать, а не изобрести ли новое соглашение?
Потом в комментариях к какому-нибудь пулл-реквесту разгораются дебаты: а какие соглашения актуальны, а точно ли им надо следовать, а не изобрести ли новое соглашение?

Как же добиться того, чтобы соглашения помогали, а не мешали? Их нужно фиксировать. И делать это так, чтобы: 

  • было удобно пользоваться; 

  • на следование этим соглашениям требовались минимальные трудозатраты; 

  • легко было понять, какие соглашения актуальны; 

  • легко было понять, почему приняты именно такие соглашения; 

  • они были автоматизированы (в идеале).

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

  • соглашения на уровне кода; 

  • соглашения на уровне архитектуры; 

  • соглашения на уровне процессов.

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

Соглашения на уровне кода

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

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

  • Не обращаемся к ENV напрямую из кода, за исключением одного класса, где оборачиваем вызовы к ENV в методы.

  • Сервисные объекты имеют в конце названия слово Service и имеют один публичный метод call.

Такие соглашения преследуют одну прямую цель — снизить когнитивную нагрузку на читающего код разработчика, в том числе помочь ему быстрее ориентироваться в незнакомом коде. Как писал Мартин, код гораздо чаще читают, чем пишут, — разница больше чем в 10 раз. Как бы вы ни относились к Rails, в этом фреймворке на полную работает потрясающий принцип convention over configuration, что позволяет любому разработчику на Rails открыть чужой проект и сразу же довольно неплохо в нём ориентироваться.

Как фиксировать такие соглашения? Линтером. Если подходящего соглашения нет в правилах линтера, напишите собственный линт. Практически все линтеры позволяют это делать: вот пример для Go, а вот — для Ruby.

Фиксация подобных соглашений линтером даёт сразу три преимущества: 

  • Разработчик не думает об этих правилах, линтер тут же подсветит все проблемы, а зачастую ещё и сам их поправит. 

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

  • Разработчик увидит недочёт в самом начале разработки, а значит, не потратит время позже на возвращение в контекст — он сразу поправит ошибку. Соблюдать соглашение становится дёшево.

Небольшой бонус: написание правила для линтера — отличная практика для джуна. Пока он с ним разбирается, узнает больше о том, как парсится код, как строится AST, лучше поймёт язык.

Соглашения на уровне архитектуры

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

  • Используем язык Python для написания обычных сервисов и Elixir в нагруженных частях системы.

  • Бэкэнд отдаёт ошибки в таком-то формате.

  • Каждый сервис обязан отдавать метрики в prometheus, на эндпоинте /metrics, порт для отдачи метрик конфигурируется переменной окружения PROMETHEUS_PORT.

Такие соглашения, помимо снижения когнитивной нагрузки, решают ещё три задачи: 

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

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

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

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

Шаг 1 — описываем

Architecture Decision Record (ADR) — инструмент фиксации подобных соглашений. Его прелесть в том, что он фиксирует не только соглашение, но и сопутствующую информацию: почему было принято такое соглашение; какие альтернативы обсуждали; когда соглашение последний раз пересматривалось; актуально ли соглашение сейчас.

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

ADR состоит из нескольких основных блоков: 

  1. Какую проблему решает соглашение.

  2. Какие варианты решения проблемы рассматривались, их плюсы и минусы.

  3. Какой вариант выбрали в итоге.

Могут быть и дополнительные блоки — например, расчёт стоимости внедрения выбранного решения.

ADR удобнее всего вести там, где видно историю изменений и обсуждений. Мой выбор — это Github и Notion, у каждого есть свои плюсы и минусы. Преимущество Github — в наличии «из коробки» инструмента для ревью и отслеживания истории версий. Notion привлекает удобством работы с базами данных в нём, тегами и тем, что с ним легко справятся и непрограммисты.

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

Шаг 2 — автоматизируем

ADR сложнее автоматизировать, чем соглашения по коду: линтера для дизайна, кажется, пока не придумали (а жаль!). Тем не менее отчасти автоматизировать возможно и их, в зависимости от того, что это за соглашение.

Для соглашений по языкам, библиотекам, встраиванию сервисов в инфраструктуру и тому подобному создавайте и обновляйте шаблоны сервисов. Тогда при создании нового сервиса разработчик не пишет его с нуля, а копирует из шаблона и сразу получает настроенный Dockerfile, отдачу метрик и т. д.

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

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

Соглашения на уровне процессов разработки

По процессам в каждой компании есть множество соглашений, например: 

  • Описание того, как устроен наём в компанию. 

  • Описание процесса выкатки релиза. 

  • Наличие дизайн-ревью для больших задач.

  • Проведение синка два раза в неделю с обсуждением текучки.

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

Идею я подсмотрел у Виталия Шароватова на TeamleadConf Foundation 2022. К сожалению, видео его выступления пока недоступно, так что опишу кратко суть.

Виталий предложил инструмент, аналогичный ADR, — Process Decision Record (PDR). Единственное отличие — вместо архитектурных решений в нём описываются решения о процессах. Кроме того, он предложил в каждом PDR ставить «дату переосмысления» — когда вы возвращаетесь к документу, чтобы понять, решает ли он всё ещё ваши проблемы лучшим образом, через n месяцев после принятия (к слову, то же самое можно делать и с ADR).

Что касается автоматизации процессов, с ней всё сложно. Какие-то процессы вы можете автоматизировать, настроив worflow в jira, поставив напоминания для встреч или создав бота, который автоматически готовит презентацию итогов недели (я так делал, правда в чужой компании).

Но часто процессы толком не автоматизируешь, и главное — сделать так, чтобы следовать им было проще, чем не следовать. Тем не менее фиксация соглашений всё-таки будет полезна, даже если вашим процессам уже легко следовать, — формализация и рационализация позволят улучшать процессы.

И что в итоге?

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

Может показаться, что всё это ненужная бюрократия, ведь «мы нормальные пацаны — и так всё порешаем». Но на самом деле соглашения значительно экономят время и берегут нервные клетки сотрудников. Конечно, не нужно возводить их в абсолют и отвергать любые вещи, идущие вразрез с договорённостями, — это могут быть признаки того, что соглашение устарело или изначально вы не подумали о каких-то его аспектах.

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

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

Для каждого нового типа соглашений проходите три этапа: 

  1. Придумайте, как его фиксировать.

  2. Придумайте, как его автоматизировать.

  3. По прошествии времени убедитесь, что соглашение экономит вам больше ресурсов, чем тратится на его поддержание.

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

Комментарии (15)


  1. fo_otman
    24.08.2022 19:38
    +2

    Есть идея создать инструмент для написания и поддержки ТЗ. Который будет частично перекрывать по функционалу то, что описано в статье. Будет включать в себя удобство как в Google Docs, версионирование как в Git и подсветку изменений как в Diffchecker. Эдакое ТЗ с удобным просмотром истории изменений. С меня бэкенд, но 80% работы там на фронте. Есть тут желающие контрибьютить свободное ПО?)


    1. Houston
      24.08.2022 21:17
      +2

      Сделать Google Docs в свободное от работы время? Ну да, ну да)


    1. sciomenihilscire
      25.08.2022 15:10

      так есть же markdown и git


      1. fo_otman
        25.08.2022 15:15

        Да, формат удобнее всего Markdown. Git хорошая вещь, но менеджеры с ним не работают, это слишком специализированный инструмент. На фронте будет editor.js, сохраняться в базу с комментарием о причинах правки, автором, датой правки, автоинкрементации версии. Затем будет страница типа /?from=1.23&to=1.24, которая в удобном виде подсветит разницу между 2мя версиями, как в DiffChecker. В публичном доступе ТЗ будет выглядеть, как любая типичная документация. А под капотом - крутые фичи, облегчающие жизнь компании. Больше никаких неактуальных ТЗ, никаких правок мимо ТЗ, никаких забытых договоренностей, никаких белых пятен в логике.


    1. TonyKentnarEarth
      25.08.2022 20:52

      notion, confluence, wiki gitlab'а/bitbucket'а/github'а - тысячи их))


      1. fo_otman
        25.08.2022 20:55

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


        1. asolokha
          26.08.2022 13:53

          Плагин к obsidian допишите, git там уже есть. Хотя вроде уже и встречал плагин версионки так что даже не с пустого места начнете


  1. Waversz
    24.08.2022 19:40
    +1

    Не слишком ли усложнено? Написали требования, повесили на стеночку, шаг вправо, шаг влево - расстрел. Если возникает непонятка - на консультацию к старшим.

    Хотя... в больших проектах без этого наверное никак.


    1. KeySmasher
      25.08.2022 13:17

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


  1. andreishe
    25.08.2022 02:30
    +1

    Как бы вы ни относились к Rails, в этом фреймворке на полную работает потрясающий принцип convention over configuration

    Да ладно. Фреймворков много, а я - один. И конвеншены везде свои. Я за то, чтобы было понятно, что код делает с минимальной (идеально без) подготовкой. Обилие соглашенческой магии этому не способствует.


    1. 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", что достаточно логично, и не требует особой подготовки.


      1. unkmas Автор
        25.08.2022 11:00
        +2

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

        Ну и конвенции не отменяют читаемого кода, а дополняют его)


  1. Yago
    25.08.2022 15:40

    Делали без ADR, более упрощенно:

    1) Выработали план, по которому принимаем соглашения на уровне стека

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

    3) Раз в неделю собирались стеком и шли по плану.

    4) В ходе обсуждений задавались конкретные вопросы и фиксировались результаты обсуждений в виде рекомендаций или обязательных правил. Фиксировались на странице вики в формате markdown, об изменениях оповещались участники процесса.

    5) В параллеле с вопросами стека применили похожую схему к процессам в команде (git-flow, ревью, детализация, тестирование, практика релизов).

    6) При дальнейших действиях в команде ее участник уже имел справочную информацию в виде документов, на которые можно опираться при принятии решений.

    7) Попробовали нововведения в бою, настроили механизм отладки соглашений, запустили переодический пересмотр.

    8) Автоматизировали лишь спустя какое-то время, и далеко не все. Потому что некоторые правила требовали достаточно весомых затрат на автоматизацию.

    9) Запустили таким образом кросс-командные соглашения.

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


  1. lxsmkv
    25.08.2022 16:04

    Интересно, конечно. С одной стороны у нас аджайл с его принципами

    Люди и взаимодействие приоритетнее процессов и инструментов .
    Сотрудничество с заказчиком важнее согласования условий контракта
    Работающий продукт важнее исчерпывающей документации
    ...

    С другой стороны мы вводим формализацию и инструменты для ее автоматизации.

    P.S.: Кстати, если смотреть на контрактную разработку то корпоративным заказчикам по факту важнее именно ценности справа, а не слева, хоть они и требуют чтобы их команды работали по аджайлу. Единственный принцип корпоративного аджайла это "прикрытие своей задницы важнее всего остального".


    1. unkmas Автор
      25.08.2022 18:37
      +1

      Так это не противопоставление. Это фраза не про то, что не должно быть процессов, она про то, что процессы надо адаптировать под свою команду, а не копировать жёстко практики. Люди важнее, но у нас же всё равно есть какой-то процесс, так? Этот процесс может быть осмысленным, а может быть и нет. Вот я и предлагаю осмыслять и фиксировать осмысление.