Сколько времени должно пройти, чтобы узнать, насколько безопасен ваш код? Мартин Фаулер однажды сказал: 10 минут. Десять лет спустя пятьсот разработчиков согласились. Я не согласен - ни с кем из них. Во-первых, десять минут недостаточно для правильной сборки, даже для небольшой программной системы. Во-вторых, десять минут слишком много для сборки, которую мы запускаем из IDE после каждого правки в одной строке. Нам нужна более детализированная классификация сборок: от моментальных до тщательных и медленных.
Как долго должна продолжаться сборка?
Столько, сколько это необходимо, чтобы пройти все необходимые тесты. Неправильно. Представьте себе сборку, которая занимает час. Скорость разработки будет близка к нулю. Программисты будут жаловаться. Много.
А что насчет сборки, которая занимает несколько секунд? Скорость написания кода будет высокой - но не надолго. Качество кода будет под угрозой, потому что быстрая сборка означает слабое тестирование, полное моков.
Итак, сколько времени должна длиться сборка? Правильный ответ: от нескольких секунд до нескольких часов. Но сборки должны быть разными.
Первая сборка - та, которую мы запускаем на наших ноутбуках. Она быстрая. Она включает только модульные тесты. Каждый из них занимает несколько миллисекунд. Даже если их много, все вместе они могут занимать меньше нескольких секунд. Мы также проверяем пороги покрытия тестами.
Если локальная сборка занимает больше нескольких секунд, она начинает становиться обязанностью, а не помощью. Мы не запускаем такую сборку, потому что наслаждаемся уверенностью, которую она обеспечивает. Мы запускаем ее, потому что это “правильно”. Такая сборка не приносит удовольствия, а лишь надоедлива. Долгая сборка нарушает ритм энергичного кодирования.
Что насчет компиляции? Десять секунд включают время компиляции исходных файлов. Что, если проект содержит сотни крупных файлов на C++, которые требуют нескольких минут на компиляцию? Мы разбиваем такой проект на более мелкие компоненты - каждый с собственной сборкой и собственным репозиторием. Мы не терпим больших кодовых баз и монолитных репозиториев.
Наконец, после нескольких десятков запусков быстрой сборки у нас достаточно уверенности, чтобы отправить запрос на объединение. После его отправки GitHub его обрабатывает и запускает наши рабочие процессы.
У нас их много, в разных YAML-файлах. Помимо модульных тестов, они запускают интеграционные тесты и все виды проверок стиля. Мы понимаем, что вероятность сбоя высока, потому что модульные тесты, которые мы запускали в быстрой сборке, составляют только часть всех тестов.
Мы ждем несколько минут и видим, что некоторые рабочие процессы завершаются неудачно. Мы исследуем сбой, задавая модульным тестам вопрос: “Почему ты этого не поймал?” Когда мы находим ответ, мы исправляем тесты или создаем новые. Затем мы отправляем и снова ждем несколько минут. Мы видим новый сбой и повторяем цикл, который обычно повторяется несколько раз на ветке. В конце концов мы видим, что все рабочие процессы GitHub становятся зелеными.
Мы платим за это упражнение. Во-первых, GitHub берет плату за минуту. Во-вторых, проект платит нам, пока мы бездействуем, ожидая ответа от GitHub Actions. Вот почему мы хотим, чтобы сборка занимала менее десяти минут - она должна быть дешевой.
Когда все рабочие процессы GitHub зеленые, мы нажимаем кнопку, запрашивая у Rultor или GitHub Merge Queue — чтобы объединить их. Предварительная сборка начинается и занимает до часа на экземпляре AWS EC2 по требованию. Это занимает так много времени, потому что, помимо модульных и интеграционных тестов, например, выполняется мутационное тестирование. Даже в небольшой кодовой базе десять минут может быть недостаточно.
Предварительные сборки также могут выполнять нагрузочные, стрессовые, производительностные, веб-браузерные и тесты на проникновение в безопасность. Мы не ожидаем, что они сломаются после успешной дешевой сборки. Однако это иногда происходит. Если сборка сломалась, мы возвращаемся к быстрой сборке, обвиняя сеть безопасности в небрежности. Мы воспроизводим ошибку с новым модульным тестом и делаем еще одну попытку объединения, ожидая, что на этот раз предварительная сборка пройдет успешно. В конце концов, это происходит, и код отправляется в ветку master
.
Иногда мы выпускаем новую версию продукта - это может быть библиотека или микросервис. В этот момент приоритетом является качество, в то время как длительность сборки не имеет значения. Мы нажимаем кнопку и ждем, сколько потребуется. Иногда несколько часов.
В отличие от всех предыдущих сборок, эта сборка правильная. Помимо всех вышеупомянутых тестов, она включает в себя, например, тесты в мультибраузерной облачной среде, автоматизированные A/B тесты и все виды регрессионных тестов. В будущем, безусловно, мы будем запускать тесты на основе LLM для обнаружения несоответствий в дизайне и уязвимостей в безопасности.
Почему мы не запускали эти тесты во время сборки предварительной проверки? Чтобы избежать переполнения очереди слияний. Продуктивный программист может отправлять до пяти запросов на слияние в день. С десятью активными участниками команды мы можем получить несколько десятков слияний в день. Поскольку очередь слияний не может быть параллелизирована, даже один час для предварительной проверки может быть слишком долгим. Правильная сборка определенно не подойдет.
Кроме того, интеграция с производственной средой занимает много времени. Мы вносим изменения в базу данных, применяем миграции данных, обновляем конфигурации AWS и переключаемся между “зеленой” и “синей” средой.
В небольших проектах дешевая и проверочная сборки могут быть похожи. В крошечных проектах все три сборки - дешевая, проверочная и полноценная - могут быть идентичны. Однако мы всегда сохраняем быструю сборку отличной от остальных. Фреймворк “четыре сборки” можно сократить до “двух сборок”, но никогда до “одной сборки подходят все”.
Translated by ChatGPT gpt-3.5-turbo/42 on 2025-04-13 at 04:41