{ "index": 45, "slug": "editorial-2026-10-practice-code-review-standard", "title": "Code review без шума: как проверить риск изменения", "excerpt": "Практический стандарт для code review: сначала назвать границу изменения и цену ошибки, затем запросить нужное доказательство и остановиться, если сильный вывод пока не подтверждён.", "contentHtml": "
В pull request может быть двадцать комментариев, но ни одного вопроса о данных, миграции или отказе. Reviewer исправляет имя переменной и форматирование. В это время изменение меняет nullable-поле, порядок переходов состояния или правило доступа. Симптом виден сразу: обсуждение длинное, а главный риск не назван. Цена ошибки — несовместимый потребитель, повторная операция после timeout или доступ к данным не той роли.
\nХорошее review не обязано находить все дефекты и не обещает идеальный код. Его задача уже выполнена, если оно связывает наблюдаемый факт с границей изменения, проверкой и допустимым действием. Если факта не хватает, reviewer ограничивает вывод: не пишет «совместимо» или «безопасно», а называет, какой вопрос ещё требуется закрыть.
\nНачните с цели изменения, а не с первой строки diff. Что должен получить пользователь, потребитель API или оператор? Затем проверьте дизайн, поведение, сложность, тесты, имена, комментарии, стиль и документацию. Такой порядок совпадает с областями, перечисленными в руководстве Google Engineering Practices, но он не является универсальной политикой: команда может добавить свои требования к миграциям, данным или доступам.
\nРазделите обязательное и необязательное. Нарушенная граница контракта — причина для точного вопроса или остановки. Неудачное имя, которое не меняет смысл и не противоречит style guide, — отдельная рекомендация. Когда оба типа замечаний лежат в одном списке без меток, автор тратит внимание на косметику, а дорогой риск выглядит равным запятой.
\nУ любого diff есть граница, через которую меняется ожидание другой части системы. Для контракта это форма JSON, тип поля, код ошибки или порядок вызовов. Для поведения во времени — состояния, timeout, retry и побочный эффект. Для доступа — доверенная сторона, входное правило и запрещённый результат. Для style-only изменения граница остаётся локальной: меняется читаемость, но не наблюдаемое поведение.
\nКласс риска выбирает следующий вопрос. При nullable-поле сравните старую и новую форму, найдите потребителей и опишите переход. При retry покажите состояние до сбоя, событие, состояние после него и результат повтора. При проверке роли отделите значение из запроса от решения, кто имеет право его передать. Фраза «это только рефакторинг» не закрывает проверку: попросите назвать invariant — свойство, которое должно остаться прежним, — и способ его проверить.
\nEvidence — именованный факт, который другой инженер может проверить в рамках задачи. Ссылка на файл показывает место, но не обязательно показывает всех потребителей. Зелёный тест подтверждает один сценарий, но не объясняет повтор после timeout. Три изменённых файла показывают объём diff, но не доказывают, что откат возможен.
\nПривяжите каждый риск к минимальному набору доказательств. Для контракта это schemaDelta, consumerMap и rollbackNote. Для поведения — stateTransition, failureMode и граница наблюдения. Для доступа — trustBoundary, правило входа и последствие нарушения. Не просите «проверить всё»: такой запрос нельзя завершить и нельзя воспроизвести.
Отделяйте факт от вывода. «Поле стало nullable» — факт. «Все клиенты готовы» — вывод, для которого нужна карта клиентов и проверка их обработки null. «Тест прошёл» — факт о запуске. «Повтор безопасен» — более сильное утверждение, которое требует отрицательного сценария. Если набор неполон, корректный результат — stop с конкретным missing evidence.
| Наблюдаемый симптом | Риск | Минимальная проверка | Допустимое действие |
|---|---|---|---|
| Поле ответа стало nullable | Контракт | Сравнить формы и перечислить потребителей старого типа | Запросить карту потребителей и обработку null |
| После timeout операция повторяется | Поведение во времени | Проследить state до сбоя, повтор и побочный эффект | Оставить вопрос до проверки идемпотентности |
| Роль приходит из тела запроса | Граница доверия | Найти серверную связь роли с authenticated user | Передать узкий вопрос владельцу доступа |
| В обсуждении только форматирование | Риск вытеснен стилем | Проверить цель, вход, выход и invariant | Разделить обязательный риск и style-note |
| Тест зелёный, но проверен только happy path | Ложное покрытие | Сломать условие и убедиться, что тест падает | Добавить отрицательный сценарий или ограничить вывод |
Ниже — небольшой TypeScript-валидатор. Он не читает репозиторий, не запускает CI и не оценивает production. Функция проверяет только полноту карточки: если для контрактного изменения нет карты потребителей, она возвращает stop. Это полезный механизм для шаблона комментария, но не автоматическое разрешение слияния.
\ntype Risk = 'contract' | 'operational' | 'security' | 'style-only';\n\ntype ReviewCard = {\n risk: Risk;\n boundary: string;\n question: string;\n evidence: string[];\n action: 'comment' | 'stop' | 'escalate';\n};\n\nfunction assess(card: ReviewCard) {\n const missing = [\n ['boundary', card.boundary],\n ['question', card.question],\n ['evidence', card.evidence.join(', ')]\n ].filter(([, value]) => value.trim() === '')\n .map(([name]) => name);\n\n if (missing.length > 0) {\n return { status: 'stop-missing-input', missing };\n }\n\n if (card.risk === 'contract' &&\n !card.evidence.includes('consumerMap')) {\n return { status: 'stop-missing-consumer-map', missing: ['consumerMap'] };\n }\n\n return { status: card.action + '-question-ready', missing: [] };\n}\n\nconsole.log(assess({\n risk: 'contract',\n boundary: 'discount: number -> number | null',\n question: 'which consumers handle null?',\n evidence: ['schemaDelta'],\n action: 'stop'\n}));\n// { status: 'stop-missing-consumer-map', missing: ['consumerMap'] }\nПроверяемое свойство примера узкое: при отсутствии consumerMap функция не сообщает о совместимости. Если добавить карту, результат станет stop-question-ready, потому что в вызове выбрано действие stop. Даже тогда карточка не доказывает, что каждый потребитель действительно обработан. Она лишь делает следующий вопрос явным.
Начните с факта: «Поле discount стало nullable». Затем назовите последствие: «старый потребитель может распаковать значение без проверки». Дайте проверку: «покажите список потребителей и тест обработки null». Завершите границей вывода: «до этого нельзя утверждать совместимость». В таком комментарии есть наблюдение, причина запроса и следующее действие.
Один комментарий — одно решение. Обязательное замечание о контракте не прячьте среди предложения переименовать функцию. Для локального стиля используйте явную метку вроде Nit или «необязательно», если это соответствует правилам вашей системы review. В руководстве Google такая маркировка отделяет пожелание от требования; это снижает риск, что автор примет личное предпочтение за блокирующее условие.
Если вопрос требует другой компетенции, передайте его владельцу границы, но не приписывайте ему диагноз. Для безопасности это может быть вопрос о модели доверия, для эксплуатации — о повторе побочного эффекта, для контракта — о совместимости потребителей. Передача должна содержать факты, точный вопрос и известное ограничение. Approval, merge и выпуск — отдельные решения, а не следствие одной заполненной карточки.
\nЭта схема не заменяет дизайн-документ, threat model, контрактные тесты, нагрузочную проверку, аудит доступа или план миграции. Она не доказывает отсутствие уязвимости и не считает severity. Её функция скромнее: не позволить выводу стать сильнее доступных фактов.
\nДля style-only изменения карта потребителей создаст лишнюю процедуру, если граница поведения действительно доказанно не меняется. Для финансовой операции потребуются дополнительные строки об идемпотентности, аудите и сверке. Для публичного API важны версия, период совместимости и план удаления старой формы. Для персональных данных добавьте права, срок хранения и путь удаления. Расширяйте матрицу только теми условиями, которые принадлежат конкретному риску.
\nЕсть и предел статического review. Скрытая динамическая маршрутизация, конфигурация, внешняя интеграция и race condition могут находиться за пределами доступного diff. В этом случае reviewer фиксирует область, которую проверил, и остаточный вопрос. Честный stop полезнее уверенного «безопасно», если подтверждающего эксперимента ещё нет.
\nReview-вопрос готов, когда другой инженер без догадок видит симптом, границу, цену ошибки, нужное evidence, отрицательный путь и следующее действие. Для обязательного замечания понятен владелец проверки. Для style-note ясно, что она не блокирует поведение. Для положительного результата указано, что именно проверено и какое утверждение всё ещё запрещено.
\nПеред отправкой комментария перечитайте его как короткую карточку: факт → риск → доказательство → действие → ограничение. Если в ней осталось «выглядит хорошо», «всё совместимо» или «тесты зелёные» без конкретного свойства, вернитесь к границе изменения. Это не формальность: так обсуждение остаётся воспроизводимым после смены автора и reviewer.
\n