На пути к поддерживаемому эликсиру: процесс разработки

Это первая из серии статей, в которых мы представим наш подход к созданию поддерживаемой базы кода Elixir. Чтобы дать немного контекста, Very Big Things - агентство цифровых продуктов с динамичным темпом разработки программного обеспечения. Новые проекты запускаются часто, а приоритеты иногда внезапно меняются. Интенсивный проект, разработанный сегодня, может быть отложен завтра и должен быть возобновлен через несколько недель или месяцев. Тем временем разработчики будут назначены для других проектов, и после возобновления разработки новая команда может в конечном итоге состоять из совершенно других людей.

В таких обстоятельствах крайне важно поддерживать некоторые общеорганизационные стандарты, чтобы ускорить процесс адаптации во время смены проекта. По этой причине все проекты VBT работают на одном и том же стеке технологий. В частности, в качестве внутреннего языка используется Elixir, а API чаще всего предоставляется через GraphQL. Такие библиотеки, как Ecto, Phoenix и Absinthe, также присутствуют практически в каждом бэкэнд-проекте VBT.

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

Чтобы это произошло, мы согласовали некоторую желаемую структуру кода высокого уровня (которая будет обсуждаться в следующих статьях) и набор значений и свойств, которые мы хотим получить от нашего кода. Грубо говоря, мы хотим найти простейшие решения наших нынешних проблем и избегать построения излишне общих и сложных абстракций, направленных на решение всех возможных будущих проблем. Такой код обычно проще для понимания и открыт для движения в любом произвольном направлении.

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

Процесс

Хорошо организованная кодовая база вряд ли получится случайно. Вместо этого нам нужно разработать процесс, который поможет нам обеспечить качество кода и сохранить единообразие стиля в разных проектах. При этом важно различать свойства, которые должны сохраняться всегда (или в подавляющем большинстве случаев), и свойства, которые лучше оцениваются людьми. Например, мы всегда хотим, чтобы программа работала правильно, поэтому мы можем положиться на такие инструменты, как CI, чтобы гарантировать прохождение всех тестов. С другой стороны, оценку качества тестов лучше оставить людям. Стремясь автоматически обеспечить это, например, обязательное 100% тестовое покрытие обычно будет недостаточным и даже контрпродуктивным.

Наша основная стратегия - выполнять всю работу через запросы на вытягивание. Мы делаем некоторые ветки защищенными, например, разрабатываем или выпускаем, и никто не может вносить изменения напрямую в эти ветки. Вместо этого такие ветви могут продвигаться дальше только через запросы на вытягивание, которые можно объединить, только если все автоматические проверки CI пройдены и PR одобрен хотя бы одним рецензентом. Таким образом мы можем гарантировать, что некоторые важные свойства всегда выполняются (например, код успешно компилируется и все тесты проходят).

Наша сборка CI состоит из обычных подозреваемых. Мы устанавливаем необходимые компоненты, получаем зависимости библиотеки, компилируем программу и запускаем тесты. Все эти шаги должны быть выполнены успешно, иначе слияние будет отключено.

При компиляции программы мы проверяем предупреждения, вызывая mix compile — warnings-as-errors. Обработка предупреждений как ошибок помогает нам поддерживать чистоту кода, упрощает переход на следующую версию Elixir и даже может обнаруживать некоторые возможные ошибки (например, вызов несуществующей функции другого модуля). Поскольку Elixir поддерживает условную компиляцию, мы выполняем эту проверку во всех трех смешанных средах (dev, test и prod). Это приводит к немного большему времени сборки, но может помочь обнаружить возможные тонкие проблемы.

Среди некоторых менее обычных проверок мы также проверяем обратимость миграции базы данных, вызывая mix ecto.rollback — all после шага теста. Еще мы делаем наивный дымовой тест релиза OTP. Во время сборки CI мы создаем выпуск OTP (в prod env) и проверяем, что приложение может быть успешно запущено, вызывая что-то вроде:

bin/my_app eval "{:ok, _} = Application.ensure_all_started(:my_app)"

Гид по стилю

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

Средство форматирования помогает нам достичь базового, согласованного макета и стиля, в то время как Credo позволяет нам улучшить согласованность кода более высокого уровня. Например, с помощью Credo мы обеспечиваем согласованный порядок элементов в каждом модуле. Для этой цели мы написали нашу специальную проверку Credo, которая теперь передается вверх по течению и доступна в Credo как Credo.Check.Readability.StrictModuleLayout.

Кроме того, Credo помогает нам обеспечить соблюдение некоторых желаемых свойств, таких как обязательные спецификации типов для всех общедоступных функций. Наконец, мы используем Credo для обнаружения нежелательных шаблонов, таких как переименование модуля с помощью псевдонимов (alias Foo, as: Bar), отдельных каналов (foo |> bar()) или сложных многострочных выражений в предложении do:.

По сути, мы рассматриваем Credo как набор инструментов для руководства по стилю. Мы используем его в строгом режиме (который включает некоторые дополнительные проверки), но мы также отключаем некоторые проверки, такие как TagTODO и ModuleDoc, которые не имеют особого смысла для наших проектов. Кроме того, иногда мы можем отключить проверку только в определенной части кода. Хорошим примером этого является проверка спецификаций типов. Как уже упоминалось, мы хотим применить спецификации типов для общедоступных функций, но спецификации типов иногда не очень полезны, например, в веб-модулях, таких как контроллеры Phoenix и преобразователи Absinthe. Таким образом, в таких модулях проверка Credo спецификаций отключена. В Credo хорошо то, что оно не очень самоуверенное. Он предлагает разумный набор правил по умолчанию, обеспечивающий более широкий набор проверок, на которые мы можем выбрать, и позволяющий нам отключать каждую проверку в каждом конкретном случае (для каждой строки или файла).

Как следствие, нам вообще не нужен документ с руководством по стилю, что снижает умственную нагрузку на команды и упрощает процесс проверки кода. Это также упрощает процедуру адаптации новых членов команды.

Поскольку настройка всех этих правил в новом проекте может быть обременительной и подверженной ошибкам, мы создали собственный генератор проектов, который настраивает все так, как мы хотим. Генератор, среди прочего, создаст конфигурацию Credo по умолчанию, базовую конфигурацию CI / CD и различные другие части, которые мы хотим иметь во всех наших проектах.

Проверки кода

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

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

Эти преимущества определенно стоят дополнительных усилий, которые даже не должны быть значительными. По моему опыту, проверка хорошо организованного PR, рассчитанного на рабочий день, обычно занимает не более 30 минут. Иногда будут возникать исключения, обычно при внесении некоторых значительных изменений (например, крупный рефакторинг или реализация сложного алгоритма), но такие ситуации должны быть менее распространенными.

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

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

  1. Добавить таблицу базы данных и схему Ecto (используя минимальный набор полей)
  2. Добавить операцию контекста регистра
  3. Добавить регистр API
  4. Добавить операцию контекста входа
  5. Добавить API входа

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

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

Во время обзора мы ищем различные потенциальные проблемы, такие как ошибки, состояния гонки, отсутствующие ограничения базы данных (например, не null, уникальность), отсутствие тестов или слабые тестовые утверждения, сочетание проблем (например, путем прямого использования Repo из веб-уровня ) и др. Кроме того, мы уделяем особое внимание ясности кода.

Если у проверяющего есть проблемы с пониманием кода, скорее всего, он недостаточно ясен и может быть решен различными способами. Как правило, автор должен внести в код некоторые изменения, которые могут быть такими простыми, как улучшение наименования или добавление краткого комментария, или сложными, требующими значительного переписывания. Это зависит от конкретной ситуации, и обеим сторонам, возможно, потребуется организовать встречу для обсуждения наилучшего курса действий. Объяснение кода через PR-комментарий редко бывает хорошей идеей, потому что это помогает только одному читателю (рецензенту), оставляя участников, которые не участвовали в обзоре, в неведении.

Наконец, стоит упомянуть, что проверка обычно не является срочным делом. Рецензент может завершить свою работу, прежде чем просматривать назначенные PR. Тем временем автор может начать новую работу, возможно, помимо ожидающих изменений, если это необходимо. Если какое-то изменение необходимо срочно объединить или если рецензия не поступит в разумный период времени (например, за один день), автор может связаться с рецензентами в чате команды.

Резюме

Наличие надлежащего процесса разработки - основная предпосылка для создания поддерживаемого кода. В VBT мы выбрали довольно легкий процесс, не связанный со сложными бюрократическими процедурами. Мы используем такие инструменты, как CI и SCM, чтобы обеспечить соблюдение как можно большего количества правил. Это оставляет меньше места для ошибок и снижает умственную нагрузку на разработчиков.

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

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

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

Чтобы это произошло, не требуется значительных временных затрат после первоначального периода корректировки. Когда все в команде адаптируются к процессу и привыкают к согласованному стилю кодирования, установленные практики становятся частью корпоративной культуры и силой привычки. За те полтора года, что я работаю с VBT, я ни разу не слышал, чтобы кто-нибудь, разработчик или менеджер, жаловался, что разработка сейчас идет медленнее. Кажется, что люди в целом довольны процессом и созданным кодом, а он в точности такой, каким должен быть. Цель процесса и дизайна кода - помочь нам, а не мешать.

Итак, теперь ли наш код идеален? Конечно, нет, но ясность, как и большинство других вещей в жизни, не бинарна. Лучше иметь, чем ничего, а еще лучше - много. Мы чувствуем, что улучшили создаваемый код, но не воспринимаем это как должное. Мы постоянно пересматриваем свои решения, основываясь на реальном опыте, и при необходимости корректируем их.