Инженерная практика
Размер PR задаёт латентность ревью и детект разом
Прия в одно утро открывает два PR. Первый — фикс бага на 40 строк; Сэм подхватывает его между встречами, читает каждую строку, оставляет один настоящий вопрос, апрувит — готово до обеда. Второй — «рефакторинг + новый export-пайплайн» на 1600 строк, который она девять дней полировала на ветке. Он висит. Ни у кого нет свободного блока в 90 минут, так что он два дня ждёт ревьюера, потом получает одиннадцать комментариев — десять про нейминг, один про строку лога — и LGTM. Логика батчинга в пайплайне, настоящий риск, так и не обсуждается. Через три недели он тихо теряет каждое событие, пришедшее во время ретрая. Малый PR получил больше настоящего ревью, чем большой, и в десять раз быстрее.
Детект пиков в узкой полосе, потом обрыв
Исследование SmartBear в Cisco — 2500 ревью на 3,2 млн строк, до сих пор крупнейшее в своём роде — нашло, что оптимум это 200–400 строк кода на ревью, изученные не дольше 60–90 минут, с выходом 70–90% дефектов: из десяти присутствующих дефектов вы найдёте семь–девять. Эту полосу ограничивают два порога. После ~400 строк детект резко падает, потому что ревьюер больше не удерживает изменение в рабочей памяти. И темп важен независимо: ревьюеры медленнее ~400 LOC/час были выше среднего в поиске дефектов, но быстрее ~450–500 LOC/час плотность дефектов оказывалась ниже среднего в 87% случаев. Детект также рушится после 60–90 минут непрерывного ревью — концентрация это истощаемый ресурс, а не константа.
Неинтуитивное следствие: большой PR не зарабатывает пропорционально больше проверки за то, что он большой. Внимание не масштабируется с размером диффа; когнитивный бюджет человека зафиксирован на нескольких сотнях строк. Так что PR на 1600 строк получает менее эффективное ревью, чем на 160, а не более. Это и есть механизм штампа без чтения — ревьюер комментирует то, что локально видно (имя, строку лога), и апрувит, потому что только это влезает в бюджет. Настоящие дефекты дизайна прячутся в гигантских диффах именно потому, что гигантские диффы — те, которые никто не может прочитать целиком.
| Размер PR | Что ревьюер реально делает | Детект | Типичный подхват |
|---|---|---|---|
< 200 LOC | Читает целиком в 5-мин окне; настоящие вопросы | Высокий | Минуты |
200–400 LOC | Оптимум: 60–90 мин, полное внимание | 70–90% выход | ~1 час |
400–800 LOC | Внимание истончается; начинается беглый просмотр | Падает | Часы |
> 800 LOC | Штамп: нитпики + LGTM, дизайн не прочитан | Рушится | Дни (ждёт свободного блока) |
Латентность — это очередь, и большие PR голодают в её начале
PR на ревью — это заблокированная работа: автор не может смёржить, часто не может чисто начать следующее, и каждый час ожидания — это замороженный прогресс. Доминирующая часть оборота обычно не само ревью; это латентность подхвата, промежуток до того, как кто-то посмотрит. А подхват — это очередь, движимая размером задачи. Данные Google — чистейшая иллюстрация: разработчики ждут в среднем меньше часа для малых изменений, но около пяти часов для очень больших, потому что ревьюер найдёт пятиминутное окно для малого CL между встречами, а изменению на 1500 строк нужен непрерывный блок, который почти не появляется в загруженном календаре. Большой PR не просто ревьюится хуже — он дольше стоит в очереди, потому что его время обслуживания превышает промежутки в чьём-либо дне.
Поэтому «ревьюй быстрее» почти всегда неверная инструкция. Нельзя усилием воли создать свободный блок в 90 минут, а давление на ревьюеров создать его означает, что они спешат и просматривают бегло — меняя выигрыш в латентности на потерю в детекте. Переменная, которой вы реально управляете, — время обслуживания: PR на 200 строк влезает в уже существующие окна. Сокращение размера PR бьёт по обеим половинам оборота — короче очередь и короче ревью — не прося никого работать быстрее или пропускать шаги.
Почему это работает
Глубокая мысль в том, что размер PR связывает две вещи, которые обычно считают компромиссом. «Тщательно, но медленно» против «быстро, но поверхностно» ощущается как ручка, которую надо где-то поставить. Это не так — для размера PR обе движутся в одну сторону. Меньший дифф ревьюится быстрее (влезает в промежутки календаря) и более тщательно (влезает в рабочую память). Единственная реальная цена — дисциплина автора декомпозировать работу, а это ровно тот навык, которого требует и trunk-based разработка. Качество и поток здесь — один рычаг, а не противоположные концы.
Разбей партию: стекинг, а не big-bang
Возражение реально: некоторые изменения действительно большие. Миграция плюс использующий её код, рефакторинг, затрагивающий сорок мест вызова — не всегда можно выкатить 200 строк. Ответ — разбить партию, а не сдаться и смёржить монолит. Разрежьте по швам, каждый из которых самостоятелен и независимо улучшает здоровье кода: сначала приземлите чистый рефакторинг (сохраняющий поведение, легко проверяемый), затем фичу поверх чистой базы, затем обвязку. Stacked diffs делают это практичным — цепочка малых зависимых PR, каждый ревьюится в своём читаемом окне, так что ревьюер никогда не сталкивается более чем с несколькими сотнями строк за раз, хотя всё изменение большое.
Это тот же принцип размера партии из lean, что движет trunk-based разработкой: малые партии вскрывают проблемы рано и дёшево; большие прячут их, пока они не сдетонируют вместе. PR на 1600 строк — одна огромная партия, которую нельзя частично заапрувить: всё-или-ничего, а «всё» означает штамп без чтения. Четыре PR по 400 строк — четыре малые партии, каждая получает настоящее внимание, быстрый подхват и возможность быть отклонённой или починенной в изоляции. Автор платит небольшую цену декомпозиции заранее; команда избегает гораздо большей цены — нечитаемого диффа, протаскивающего непроверенный дефект.
Медианное время подхвата PR в команде ползёт к суткам, а пост-мёрж дефекты растут. Диффы становятся крупнее. Какой фикс даёт наибольший рычаг?
Почему PR на 1600 строк обычно получает МЕНЕЕ эффективное ревью, чем на 160?
По данным Google, почему малые изменения подхватывают менее чем за час, а большие ждут ~5 часов?
Упорядочьте, как слишком большой PR превращается в штамп без чтения:
- 1 Автор батчит фичу + рефакторинг + миграцию в один PR на 1600 строк
- 2 Ни у кого нет свободного блока в 90 мин, так что он стоит в очереди два дня
- 3 Ревьюер наконец открывает его; дифф превышает рабочую память
- 4 Он комментирует локально видимые мелочи (имена, строку лога) и апрувит
- 5 Настоящий дефект дизайна уезжает непроверенным и всплывает неделями позже
- 01Коллега доказывает: «больший PR даёт ревьюеру больше контекста, так что он должен ловить больше багов». Объясните, почему данные говорят обратное.
- 02Почему «ревьюй быстрее» обычно неверная инструкция, и какую переменную надо менять вместо этого?
Размер PR — главная переменная code review, потому что он задаёт латентность и детект одновременно и в одну сторону. Детект пиков на 200–400 LOC, изученных за 60–90 минут — выход 70–90% — и обрывается после ~400 строк и ~450–500 LOC/час, потому что внимание ревьюера это фиксированный когнитивный бюджет, не масштабирующийся с размером диффа; PR на 1600 строк поэтому получает меньше настоящей проверки, чем на 160, а штамп без чтения (нитпики плюс LGTM) — предсказуемый итог. Латентность рассказывает ту же историю со стороны очереди: подхват доминирует оборот и растёт с размером, так что Google видит малые CL подхваченными менее чем за час, а очень большие ждущими около пяти, потому что малый дифф влезает в пятиминутный промежуток, а большому нужен блок, которого ни у кого нет. Поэтому «ревьюй быстрее» и SLA дают обратный эффект — они давят на симптом и вынуждают беглый просмотр. Настоящий рычаг — время обслуживания: держите PR малыми и декомпозируйте действительно крупную работу в stacked diffs, чтобы даже большое изменение ревьюилось как последовательность читаемых окон. Малые PR режут латентность и поднимают качество вместе; единственная цена — дисциплина автора декомпозировать.