23 September 2015

Как работают над Chromium

IT systems testingGoogle ChromeBrowsers
image
Предисловие от автора оригинала:
В марте 2011 я написал черновик статьи про то, как команда, отвечающая за Google Chrome, разрабатывает и выпускает свой продукт — после чего я благополучно о нем забыл. Лишь несколько дней назад я случайно наткнулся на него. Пусть местами он уже устарел (Chrome форкнул WebKit в Blink в 2013 году, да и я сам больше не работаю в Google), я склонен считать, что изложенные в нем идеи все еще в силе.
Сегодня я собираюсь рассказать вам о том, как работает Chromium. Нет, речь пойдет не совсем о браузере Chrome, а скорее о Chromium — группе людей, занимающихся созданием браузера.

Над проектом Chromium работают сотни инженеров. Вместе мы коммитим в кодовую базу примерно 800 изменений каждую неделю. Мы также зависим от многих других больших и активно развивающихся проектов вроде V8, Skia и WebKit.

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

Каким образом все это продолжает работать? Почему «колеса» у этого «автобуса» еще не «отвалились»? Почему еще не все разработчики сошли с ума?

С технологической точки зрения, скорость работы команды Chromium стала достижима благодаря надежным, эффективным и «тихим» авто-обновлениям.

С точки зрения человеческих ресурсов, в этом есть заслуга преданных своему делу, трудолюбивых и умных команд QA и релиз-инженеров, без которых весь проект развалился бы на части в считанные недели. А еще — дизайнеров, продакт-менеджеров, писателей, PR, адвокатов, ИБ и всех остальных, кто слаженно работает над каждым стабильным релизом. Сегодня я не буду рассказывать обо всех, сконцентрировавшись только на инженерной тематике, дабы не скатиться в гигантский пост в духе Стива Йегге.

Я собираюсь рассказать о процессе разработки Chromium, который выстроен специально для того, чтобы сделать возможными быстрые релизы. Речь пойдет об интересных находках, которые могут пригодиться другим проектам вне зависимости от того, как выглядит их график релизов; упомяну я и о наших трудностях.

Без веток


Во многих проектах общепринятой практикой является создание ветки для работы над новыми «мажорными» фичами. Идея при таком выборе заключается в том, что дестабилизация из-за нового кода не будет влиять на других разработчиков и пользователей. Как только фича завершена, она мерджится обратно в trunk, после чего обычно следует период нестабильности на то время, пока устраняются интеграционные вопросы.

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

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

Приятный побочный эффект подобного процесса: в проекте нет специальной команды разработчиков «второго сорта», которые занимаются исключительно работой с эксплуатационной веткой. Все разработчики всегда работают с последней актуальной версией исходного кода.

Переключатели среды выполнения


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

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

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

Огромный объем автоматического тестирования


Для того, чтобы релизиться каждый день, мы должны быть уверены в том, что наша кодовая база всегда находится в надлежащем состоянии. Это требует автоматизированных тестов, причем очень большого их числа. На тот момент, когда пишутся эти строки, у Chrome есть 12k юнит тестов уровня классов, 2k автоматизированных интеграционных тестов и огромные ассортимент тестов производительности, bloat-тесты, тесты на thread safety и memory safety, а также, возможно, многие другие, о которых я сейчас не вспомню. И все это только для одного Chrome; WebKit, V8 и остальные наши зависимости тестируются самостоятельно; у одного WebKit насчитывается примерно 27k тестов, которые удостоверяются, что веб-страницы отображаются и функционируют корректно. Наше основное правило заключается в том, что каждое изменение должно идти вместе с тестами.

Мы используем публичный buildbot, который постоянно прогоняет новые изменения в нашем коде на тестовом наборе (test suite). Мы придерживаемся политики «зеленого дерева»: если изменение «ломает» тест, то оно тут же откатывается, а разработчик должен пофиксить изменения и перевыложить их. Мы не оставляем подобных «критические» изменений в дереве, поскольку:

  • Это упрощает возможность случайно сделать еще больше «критических» изменений — если дерево и так красное, то никто не замечает, когда оно становится еще краснее
  • Это замедляет разработку, поскольку каждый будет вынужден работать над тем, что сломалось
  • Это поощряет разработчиков делать небрежные быстрые фиксы для того, чтобы пройти тесты
  • Это не дает нам релизиться!


Чтобы помочь разработчикам избежать неприятностей с деревом, у нас есть try-боты, которые являются способом «прогнать» изменение подо всеми тестами и конфигурациями перед тем, как выпустить его. Результаты отправляются на email разработчику. Еще у нас есть commit queue, которая служит для теста изменения и его автоматического применения, если все тесты пройдут успешно. Мне нравится пользоваться ею после долгой ночи, проведенной за хакингом — я нажимаю кнопку, ложусь спать, и спустя какое-то время просыпаюсь с надеждой на то, что мое изменение влито.

Благодаря всему производимому автоматическому тестированию, мы можем отделаться минимальным количеством ручного тестирования на нашем канале dev, а на «канарейках» не проводить его совсем.

Безжалостный рефакторинг


Поскольку у нас есть достаточно обширное покрытие тестами, мы можем позволить себе быть агрессивными в рефакторинге. В проекте Chrome все время идет работа над рефакторингом в нескольких основных направлениях — например, на период 2013 года таковыми являлись Carnitas и Aura.

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

DEPS


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

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

Ежедневно несколько раз в день инженер обновляет номер версии, выясняет, возникли ли новые проблемы при интеграции, и присваивает баги подходящим инженерам. В результате, мы всегда получаем мелкие изменения в WebKit все сразу, и при подобном подходе оказываемый на наше дерево исходников эффект обычно минимален. Мы также добавили ботов к buildbot WebKit'a, благодаря чему когда инженеры WebKit делают изменение, которое поломает Chrome, они узнают об этом немедленно.

Большим преимуществом системы DEPS является то, что мы можем добавлять изменения к своей веб-платформе очень быстро. Появившаяся в WebKit фича станет доступной пользователям Chrome на канале canary в течение всего лишь нескольких дней. Это сподвигло нас к тому, чтобы делать улучшения сразу в апстриме WebKit, где они пригодятся всем, кто использует WebKit в своих приложениях, а не применять их локально у себя в Chrome. Честно говоря, нашим основным правилом является то, что мы вообще не делаем локальных изменений в WebKit (как и в остальных проектах, от которых зависит Chrome).

Проблемы


Тщательное тестирование остается неразрешенным вопросом. В частности, «нестабильные» интеграционные тесты (flaky integration tests) стали для нас постоянной проблемой. Chrome — большой, сложный, асинхронный, мультипроцессовый и многопоточный. Так что для интеграционных тестов проще простого проваливаться из-за едва различимых проблем синхронизации, что и происходит время от времени. На проекте наших размеров, тест, который фейлится 1% времени, в итоге гарантировано будет фейлиться по несколько раз в день.

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

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

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

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

В заключение


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

Эти техники будут наиболее полезны большим проектам, у которых есть быстро изменяющиеся апстрим-зависимости, но вполне возможно, что некоторые из них окажутся применимы и к проектам меньших размеров.
Tags:chromium
Hubs: IT systems testing Google Chrome Browsers
Comments 51
Ads