В сентябре далёкого 2012 года я трудился начинающим инженером в Google, занимаясь разработкой
Bazel (инструмент сборки, внутри компании также известный под именем Blaze). Однажды мне на почту пришло загадочное приглашение из Google Календаря. Его прислали два инженера из США, пригласив на встречу меня и моего тимлида.
Я сразу узнал имена отправителей — это были Роб Пайк и Расс Кокс. И хотя работать мне с ними не доводилось, я был о них наслышан.
Расса Кокса я знал по его блогу, который любил читать, а
Роба Пайка просто, потому что он известен. В ходе встречи они поделились с нами своим амбициозным планом: переформатировать каждый BUILD-файл Bazel в кодовой базе Google с помощью автоматизированного скрипта.
▍ Форматирование кода
В те времена форматировщики ещё не были распространены. Тогда не существовало таких решений, как Python Black, Clang Format или Prettier. Везде в основном использовался
gofmt. Поскольку Расс и Роб ранее работали в команде Go, они хотели воссоздать подобное решение в отношении файлов BUILD в Bazel.
Файлы BUILD имели непоследовательное форматирование, в них смешивались различные стили отступов, и единое руководство по стилю отсутствовало. Тогда было легко заметить скопированные извне блоки кода, так как их форматирование выделялось. Дискуссии на тему руководств по стилю показали, что среди инженеров на этот счёт есть масса разногласий. Предыдущая попытка написать инструмент форматирования так и не была полноценно реализована. У этого инструмента были проблемы, и внедрить его оказалось трудно.
Расс уже разработал новую версию этого инструмента на Go, назвав её Buildifier. В некоторых случаях
писать форматировщик бывает весьма сложно, но эта сложность зависит от способа решения ряда вопросов:
- Как поступать с комментариями? Buildifier прикрепляет их к ближайшему узлу в синтаксическом дереве (эта структура данных не особо детализирована, поэтому в некоторых случаях Buildifier смещает комментарий).
- Как разделять строки? Buildifier разделяет их в нескольких жёстко прописанных случаях, но при этом длина строк его не волнует. Это значительно всё упрощает.
- Как сохранить существующее разделение строк? Buildifier сохраняет некоторые из них в определённых местах синтаксического дерева.
- Нужно ли нам частичное форматирование? Buildifier всегда переформатирует файл целиком, а не только изменённые строки.
Благодаря такому набору решений, Buildifier получился очень простым. Его реализация выглядела многообещающей, но нужно было продумать, как он будет внедряться. Расс протестировал своё детище на всей базе кода и смог переформатировать все файлы буквально за несколько минут. Он спросил нашего разрешения внедрить инструмент и установить политику его обязательного использования для проверки файлов перед коммитом.
Идея переформатирования каждого файла BUILD казалась безумной и очень деструктивной. Учитывая, что 10 000 инженеров изменяют эти файлы в базе кода ежедневно, внедрение правила строгого форматирования выглядело пугающе. Можем ли мы отклонять каждый коммит, который не соответствует выводу байт-в-байт? Можно ли сделать правила проверки файлов перед коммитом менее жёсткими или разрешить использовать этот инструмент по желанию? Расс настаивал: «Да, его использование должно быть строго обязательным для каждого». Никаких исключений, никаких настроек, никаких личных предпочтений.
Спустя несколько дней, этот план был официально одобрен.
▍ Внедрение
Я помогал с внедрением Buildifier в разных аспектах, например, внёс изменения в грамматику для формализации языка сборки, а также написал руководство по стилю — таким образом я раз и навсегда определил, как должны выглядеть файлы BUILD. Мне также нужно было задокументировать, какие списки можно безопасно переупорядочивать, поскольку одной из особенностей Buildifier была возможность в определённых случаях сортировать списки (например, при сборке список исходных файлов иногда может иметь произвольный порядок).
Естественно, нам пришлось интегрировать Buildifier во все основные редакторы кода. Если код форматируется при сохранении, никто не столкнётся с неприятным сюрпризом при попытке его коммита. В базе кода некоторые инструменты генерировали файлы BUILD. При этом инструменты не должны пытаться сгенерировать отформатированный код сами. Эту работу они должны делегировать форматировщику, который выступает в качестве источника истины и может со временем меняться.
Как проверить, не ломает ли форматировщик код? Первой мыслью было сравнивать синтаксические деревья, но такой подход при изменении порядка списка не сработает. В Bazel была очень удобная фича под названием Bazel Query, которая могла выводить информацию о пакете. Я прописал в Bazel, для каких списков порядок не имеет значения, чтобы можно было с помощью этой фичи проверять, не влияет ли переформатирование на то, как Bazel понимает файл. Кроме того, в Google налажена прекрасная инфраструктура тестирования. И после подобного изменения тесты, конечно же, можно было выполнять, но только медленно и вычислительно затратно.
Как закоммитить изменения для 100 000+ файлов? При таких масштабах многие инструменты сдают, поэтому в Google было специальное решение, которое разделяло масштабные изменения на их меньшие наборы, которые можно было коммитить независимо. В случае конфликта этот инструмент откатывал конфликтующие файлы к прежней версии. Чтобы обойти этап утверждения кода, изменения отправлялись «глобальному утверждающему», который мог одобрять изменения в любой части базы кода Google.
▍ Итоги
Вопреки моим ожиданиям, всё прошло относительно спокойно. На внедрённое нами новое требование, как и на новый стиль форматирования, почти никто не жаловался.
Я помню прежние длительные дискуссии на тему отступов, расстановки скобок и прочего, в которых не удавалось прийти к консенсусу. Зато, когда мы внедрили Buildifier, сотрудники фактически оказались безразличны к принятым в нём решениям относительно стиля. Они просто наслаждались единообразием.
«Проблем с форматированием существовать не должно, и для нас это достаточно важный вопрос, чтобы не пожалеть своего инженерного времени на его решение. Я понимаю, априори не кажется, что автоматическое форматирование значительно изменит ситуацию. Я сам до конца этого не осознавал, когда работал в команде Go, пока мы не исключили этот этап из процессов редактирования и рецензирования кода. Надеюсь, через полгода или год, когда всё уже будет преобразовано, и мы оглянемся назад, выгоды от этого решения окажутся более наглядными». — Расс Кокс.
Итак, оправдало ли оно себя? Да, тысячекратно. Благодаря этому опыту, я осознал ценность единообразия и потенциал автоматизированных инструментов в плане повышения продуктивности.
Преимущества переформатирования стали заметны очень быстро. Buildifier не просто переформатировал наши файлы BUILD, но и радикально изменил наш подход к обслуживанию кода и внесению масштабных изменений. До этого обширные корректировки файлов BUILD давались крайне сложно. Инструменты рефакторинга пытались определить существующий стиль форматирования и всё под него подстроить, но справлялись плохо (в частности, при наличии комментариев и многострочных выражений). Как результат — в процессе код-ревью возникало много разногласий. Когда же появился Buildifier, подобные изменения стали рутиной. Он позволил улучшить множество аспектов Bazel, что раньше считалось невозможным. В результате мы также смогли исправить старые проблемы в дизайне кода.
В частности, позднее это позволило мне заменить язык сборки Bazel с Python на
Starlark.
▍ Дополнения
- Отдельная благодарность Нильтону Волпато, который написал первую версию Buildifier и помог с его доработкой.
- Почему бы не внедрить новый форматировщик без форматирования всех файлов? В этом случае при изменении файла будут возникать длинные списки изменений, что усложнит его ревью. Причём эта нагрузка затронет всех инженеров компании.
- Фактически общее число файлов составляло 193 000 в начале преобразования и 216 000 в конце. Подобные масштабные переходы, как правило, происходят параллельно с ростом кодовой базы.
Telegram-канал со скидками, розыгрышами призов и новостями IT 💻
![](https://habrastorage.org/webt/qz/89/ii/qz89iixw9lx5g284gb-t7l9fuam.png)