Как ускорить код-ревью

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

Что можно с этим сделать? Одним подходом может быть «установить чёткие дедлайны для ревьюеров и бить их палками, если что-то идёт не так». Это не системный подход, поэтому давайте его сразу пропустим и перейдём к более интересному.

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

Что такое код-ревью; какие у него цели?

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

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

Разработчик Олег пушит свои изменения напрямую в мастер, откуда эти изменения выезжают в продакшен. Если Олег сделал всё правильно и ничего не забыл, то нам повезло и мы ускорили пайплайн в этом месте. Но при этом есть разработчица Александра, которой придётся пересматривать свои ещё не смёрженные изменения с учётом только что вмёрженных изменений Олега. Если вам кажется, что это просто — ну, да, запустить rebase origin/master действительно просто, но что если изменения Олега меняют предположения о системе, из которых исходила Александра? В таком случае автоматический ребейз может либо выдать мёржконфликт (и это будет хорошо! Это может заострить внимание на нарушенных предположениях), либо, что страшнее, молча сделать вид, что всё нормально, хотя изменения Олега и Александры работают по отдельности и не работают вместе.

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

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

  • уровень интерфейсов между горизонтальными (равноправными) частями системы. Например, «чтобы показать попап, надо нарисовать в своём компоненте <Popup>{popupContent}</Popup>».
  • уровень фич. Например, «мы хотим показывать попап в такой ситуации».
  • уровень платформы. Например, «наше приложение запускается в контейнере с Node.js такой-то версии».

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

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

В этом контексте часто говорят о time to market определенной фичи, но это не очень подходящая метрика: если мы релизим одну фичу в неделю, но зато её провели через конвейер за полдня, то у нас будет отличный ttm (зарелизили за полдня!), но отвратительный throughput (в неделю только одну фичу!).

Код

На самом деле мы хотим уменьшить latency и увеличить throughput одновременно. Это возможно за счёт снижения времени на код-ревью без снижения качества.

Важный момент: это сработает, если бутылочным горлышком системы действительно является код-ревью. Если горлышко в другом месте, то усилия по улучшению процесса ревью будут бесполезны в контексте улучшения latency/throughput (но могут, например, повысить уверенность разработчиков, что тоже бывает полезным).

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

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

В код-ревью в целом можно выделить четыре стадии:

  1. Время от создания пулреквеста до начала ревью
  2. Время ревью, где ревьюер должен осознать суть изменений, на какие предположения опирается пулреквест, не нарушены ли они. (В плохих случаях здесь также идёт анализ кода на соответствие код-стайлу, в большинстве случаев это можно вынести на шаг 1 через автоматизацию)
  3. Время правок, где автор пулреквеста исправляет замечания с шага 2; после чего мы снова переходим на шаг 1, если только на шаге 2 не было никаких замечаний.
  4. Мёрж изменений, тестирование, релиз, потенциально — отлавливание проблем в продакшене.

Даже если мы не в состоянии изменить структуру, мы можем повлиять на количество итераций в цикле 1-3. Один из главных рисков — во время цикла система изменяется таким образом, что придётся ещё раз вносить изменения относительно предположений, или же отлавливать баги после; и то, и другое — переработка, необходимость которой возникла из-за долгих процессов ревью.

Чтобы снизить количество такой переработки надо снижать количество потенциально изменчивых предположений внутри пулреквеста. Как самый простой способ: делать пулреквесты небольшими, потенциально даже меньше, чем фича (таким образом, для реализации и деплоя фичи в мастер должны попасть несколько пулреквестов). Это снижает вероятность переработки тем образом, что когда Олег приходит менять, скажем, Popup, он учитывает потребителей, которые уже в мастере, и может автоматически отрефакторить все вызовы попапа на новый синтаксис.

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

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

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

Но мы же не можем релизить половину фичи! — скажете вы, и будете отчасти правы, мы не хотим, чтобы пользователь увидел наполовину готовое. Для этого можно использовать фича-флаги, эксплицируя время разработки внутри кода. Можно автоматизировать удаление ненужного кода через несколько дней после финального пулреквеста.

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

Люди

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

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

Часто говорят о роли код-ревью в ещё трёх процессах: снижение code ownership, распространение знаний о коде, обучение best practices и программированию в целом. Это всё полезные цели, но если мы достигли точки, где код-ревью само по себе стало заметной проблемой, то стоит вынести, хотя бы временно, их из критического пути в асинхронные методы коммуникации (например, рассылки и чейнджлоги).

Проблема code ownership заключается в том, что у человека возникает ощущение, что какой-то кусок кода принадлежит ему и враждебно относится к попыткам изменить этот кусок. Это решается предотвращением: если обозначить владельцами команды, то кусок кода не мой, но наш, моей команды. Это работает, если над системой работают несколько независимых команд — тогда включается механизм мы-и-они.

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

У людей помимо всего прочего есть свои biases, но у каждого человека их набор и их проявления более-менее стабильны, поэтому со временем можно составлять «портрет» ревьюера. Например, Игнат всегда находит, что откомментировать, в любом пулреквесте, поэтому назначая ревью на него, стоит предположить, что случится пинг-понг.

Большая картина

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

  1. Ошибки в продакшене — редкость, и обычно связаны с непредсказуемыми факторами: код-ревью выполняет свою главную роль повышения стабильности.
  2. Код-ревью редко превращается в пинг-понг: ревьюеры правильно и полноценно коммуницируют замечания.
  3. Время прохождения код-ревью обладает небольшой вариативностью: не бывает гигантских или чрезвычайно сложных пулреквестов.
  4. В системе нет мест, которые страшно менять: у разработчиков есть вера в то, что код-ревью поймает возможные ошибки.
  5. Через ревью проходят как мелкие фичи, так и большие инфраструктурные изменения.

Каждый из пунктов при желании можно измерять и отслеживать — но стоит сравнивать проект только с этим же проектом, для каждой системы целевые значения метрик свои: требования к надёжности в NASA и стартапе разные.

Слишком длинно, давай вкратце

  1. Пулреквесты должны быть маленькими: меньше строк кода — меньше предположений — быстрее ревью,
  2. Пулреквесты могут быть на половинку фичи, используйте фича-флаги: ранняя фиксация предположений в мастере снижает вероятность их нарушения,
  3. Чем меньше ревью требует ручного анализа, тем лучше: разделяйте систему на независимые и отграниченные интерфейсами части, чтобы снизить количество необходимых предположений.

Working More for Less

Интересный и короткий пост про оплату труда.

My colleague said that for years he would preface the theoretical discussion with a poll. He would turn to the class and ask them to imagine themselves employed at some job. Then imagine having your wage doubled for a short period of time. How many of you would work more? (The majority of the class would raise their hands.) How many of you would not change your hours worked? (A minority of hands). How many of you would work less? (A sprinkling of hands). At the end of the polling, he'd start teaching a standard theory of labor supply and using it to interpret the poll results (substitution vs. wealth effects).

Если инвертировать вопрос, то большинство студентов снова проголосует за «работать больше». Если вам очевидно почему, то вы менее оторваны от жизни, чем профессоры экономики.

Monodraw — Powerful ASCII art editor designed for the Mac

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

Как пример, набросал за пару минут такую схему:

Выглядит опрятно, хорошее нативное приложение.

И на выходе получил текстовый файл:

Текстовый файл, соответствует ожиданиям.

А можно получить и SVG:

SVG просто бонус, пока не придумал, где может пригодиться.

Стоит $9.99, но с прошлого года находится в режиме поддержки и дальше не разрабатывается.

Миниатюрная фича: убрать результат из поиска по коду

Меня радует, когда в продукте для программистов кто-то нашёл время подумать о фичах, которые не очевидны на первый взгляд.

Например, в VS Code можно убрать результат из поиска. Можно сделать простой запрос, зная, что в него попадёт немного больше, чем нужно, что в результатах будет несколько false positives — и убрать их в один клик.

Первоначальная задача наверняка стояла как «выводим результаты поиска», и в такой формулировке совсем не очевидно, что такая кнопка а) нужна и б) будет полезна.

NPR Accuracy Checklist (3x5in)

Обожаю красиво оформленные чеклисты и испытываю нежность к NPR в целом.

THE NPR ACCURACY CHECKLIST 13 THINGS THAT MUST BE DOUBLE- OR TRIPLE-CHECKED

  • AGES (Get the date of birth & do the math.)
  • DAYS, DATES (Are you sure it happened then?)
  • GRAMMAR & SPELLING (Listeners & readers notice mistakes & forget your great story.)
  • HISTORICAL “FACTS” (Don’t trust your memory.)
  • LOCATIONS (Get them right & pronounce them correctly.)
  • NAMES OF BUSINESSES, GROUPS & SCHOOLS (For the 100th time, it’s Dartmouth College!)
  • NUMBERS (Check your math. Don’t say “millions” if it’s “billions.” Learn about percent vs. percentage point.)
  • PERSONAL NAMES (Get the correct spelling & pronunciation.)
  • PRONUNCIATIONS (Not only names, but places & terms too. The dictionary and our online guide are your friends!)
  • QUOTES (Make sure they’re accurate & correctly attributed.)
  • SUPERLATIVES (Claims to be the first, best, worst, etc., are often wrong; never trust them.)
  • TITLES (CEO, president, professor, etc.)
  • WEB ADDRESSES & PHONE NUMBERS (Never report them without testing them.)

Также в 2018 Марк Меммот объявил, что количество ошибок на NPR зашкаливает, надо CQ'чить всё перед тем, как отдать редактору. CQ — cadit quaestio — означает, что журналист проверил все факты.

Браузер должен отражать ценности пользователя

Каждый браузер посылает заголовок User-Agent, в котором пытается рассказать, что это за браузер (там, конечно, полный кошмар, об этом был перевод на Фронтире). Это интересное словосочетание: агент пользователя. То есть, тот, кто действует от лица и в интересах пользователя.

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

Кроме корпораций, это ещё и агент больших сайтов, таких как Медиум или Ютуб, которые могут потратить большое количество ресурсов на разработку своей платформы с одной целью — вовлечённость (это страшное слово engagement). Как правило, это вовлечённость затем монетизируется опосредованно, через рекламу.

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

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

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

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

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

На фоне этого инди-сайт совершенно теряется, и становится меньше причин поддерживать свой независимый сайт («я получу больше лайков в Твиттере!»). Часть авторов нашла интересный компромисс: они публикуют оригинал у себя, и загружают на Медиум копию вместе с ссылкой на оригинал. Таким образом они получают и распространение Медиума, и сохраняют контроль над текстом.

Что может хотя бы чуть-чуть противостоять таким системным эффектам, как фидбек луп распространения Медиума? Агент пользователя. 11 декабря я написал твит с идеей расширения, которое автомагически переходило бы по ссылке на самостоятельно размещенную версию статьи с Медиума. Это пример того, как браузер вместе с расширениями могут точнее отражать мои намерения, могут помочь проявить мою agency, то есть, способность действовать самостоятельно.

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

Но я сейчас использую Firefox — что тоже кажется важным в свете смерти движка внутри Edge. Потому что разнообразие браузеров — тоже одна из моих ценностей.

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

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

Сейчас маятник контроля сайт/пользователь сильно на стороне сайта, и сам по себе он не качнётся в обратную сторону. Существуют Greasemonkey и Stylish, расширения, которые позволяют вмешиваться в происходящее на странице, но их аудитория во многом ограничена теми, кто уже владеет JS и CSS; существуют адблокеры разной степени честности. Нужно больше, нужно больше новых идей и расширений, меняющих опыт просмотра сети, и ещё больше расширений, которые изменят опыт авторства в сети. Нужно, чтобы идеи и расширения были настолько хорошими, чтобы у них получилось преодолеть барьер «а что такое расширения» (браузеры могут здесь, несомненно, помочь пиаром — но вряд ли будут это делать).

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

Если у вас есть такие идеи, можете поделиться в Твиттере (я осознаю иронию): @marinintim, или по почте: [email protected].