{ "index": 43, "slug": "editorial-2026-10-field-code-review-standard", "title": "Code review: как не пропустить риск изменения контракта", "excerpt": "Форматирование занимает строки в комментариях, а необратимое изменение контракта остаётся без проверки. Разбираем порядок review, который связывает симптом, evidence, отрицательный путь и решение.", "contentHtml": "
В pull request меняют поле ответа с обязательного на nullable. В комментариях спорят о названии функции, порядке импортов и длине строки. Через неделю старый клиент падает на пустом значении. Ошибка возникла не в синтаксисе. Review проверил видимый diff, но не проверил границу контракта. Цена такого пропуска — аварийный откат, срочный выпуск совместимости и потеря времени у команды, которая теперь ищет всех потребителей вслепую.
\nТезис простой: code review должен связывать каждый существенный риск с проверяемым evidence. Если изменение меняет форму данных, одного чтения строк недостаточно. Нужно назвать потребителей, переходы состояния и путь возврата. Если evidence не хватает, reviewer формулирует точный вопрос и останавливает сильный вывод. Он не заменяет пробел догадкой и не маскирует его стилевым комментарием.
\nСимптом обычно виден в обсуждении: много мелких замечаний, спор о вкусе, длинный список предложений без одного вопроса о поведении системы. Это не доказывает плохой review. Но это сигнал проверить, не вытеснил ли стиль риск. Причина часто лежит за пределами изменённого файла: у поля есть другой consumer, миграция не обратима, а тест покрывает только новый путь.
\nНачните с вопроса: что изменится для пользователя или соседнего сервиса, если этот diff попадёт в основную ветку? Ответ должен быть конкретным. «Станет современнее» не подходит. «Клиент, который не различает null и отсутствие поля, получит другой результат» — подходит. Следующий вопрос: каким артефактом это можно проверить? Это может быть schema delta, карта потребителей, тест на старую форму или явная инструкция отката. Список должен быть конечным.
\nУдобно хранить review как короткую связку из пяти полей: change, risk, evidence, status и next action. Change называет один предмет. Risk описывает тип последствий, а не эмоциональную оценку. Evidence перечисляет входы, которыми можно проверить риск. Status показывает границу текущего вывода. Next action говорит, что должен сделать следующий владелец.
\nchange: fixed-nullable-discount-contract\nrisk: contract-migration\nevidence:\n - fixed-schema-delta\n - fixed-consumer-map\n - fixed-rollback-note\nstatus: evidence-map-ready\nnext: ask-contract-owner-to-confirm-consumers\nИмена в примере учебные. Они не ссылаются на настоящий репозиторий, pull request или production-систему. Их задача — показать форму записи. В реальном review вместо них нужны ссылки на существующие артефакты и владелец каждого из них.
\nЭта модель снижает силу вывода до уровня входных данных. Полная карта потребителей позволяет задать вопрос о совместимости. Она не доказывает, что каждый клиент уже обновлён. Schema delta показывает изменение формы. Она не доказывает, что миграция обратима. Rollback note описывает возможный путь возврата. Он не доказывает, что команда успеет выполнить его в аварии.
\nПредставьте учебный API ответа со скидкой. Было discount: number, стало discount: number | null. Сервер может собрать ответ, а новый тест может пройти. Но старый клиент способен сразу передать значение в арифметику или отрисовать его без ветки для null. Поэтому строка изменения ещё не является достаточным evidence.
type Price = {\n amount: number;\n discount: number | null;\n};\n\nfunction total(price: Price) {\n // Учебный пример: null нельзя молча считать скидкой.\n if (price.discount === null) return price.amount;\n return price.amount - price.discount;\n}\nВ этом фрагменте проверяется только локальное правило функции. Он не проверяет всех клиентов и не показывает результат выпуска. Чтобы review был содержательным, нужно найти границу потребления: кто декодирует ответ, какие значения разрешает его схема, что делает старый код и как тестируется несовместимая форма. Если карты нет, правильный комментарий звучит так: «Нужен список потребителей поля и их поведение при null. Без него нельзя оценить охват изменения». Это вопрос, а не вердикт о качестве автора.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Комментарии заполнены форматированием | Риск поведения не назван | Сверить diff с целью и контрактом | Снять style-only комментарии и задать один вопрос о последствиях |
| Поле стало nullable | Не видны все consumers | Проверить schema delta и карту потребителей | Запросить конкретный список клиентов и обработку null |
| Есть слово rollback | Не описано, что возвращается | Сопоставить старую форму и переход состояния | Попросить шаг возврата и условие его применимости |
| Тест проходит только на новом ответе | Отрицательный путь отсутствует | Подать старую форму и null | Добавить проверку отказа или безопасного значения |
| Автор просит approve при неполном input | Вывод сильнее evidence | Проверить обязательные поля risk-класса | Остановить review с перечнем недостающих данных |
Надёжный стандарт должен объяснять остановку так же ясно, как положительный путь. Если отсутствует consumer map, статус — «недостаточно evidence», а действие — запросить только карту. Не нужно добавлять «вероятно безопасно» или искать потребителей по памяти. Если reviewer видит только изменение стиля, а риск относится к контракту, стилевой комментарий не закрывает проверку. Если risk class неизвестен, сначала нужно назвать его границы.
\nЕсть и другой стоп-сигнал: все обязательные артефакты перечислены, но итоговая фраза говорит «approve and merge». Полный набор входов не превращает учебную карточку в разрешение на слияние. В настоящем процессе approval зависит от полномочий, политики репозитория и результата остальных проверок. В записи review лучше разделять «evidence достаточно для следующего вопроса» и «изменение готово к merge».
\nfunction nextReviewAction(review) {\n if (!review.consumerMap) {\n return { status: 'stop-insufficient-evidence',\n action: 'request-consumer-map' };\n }\n\n if (review.risk === 'contract-migration' && review.decision === 'style-note') {\n return { status: 'stop-style-displaces-risk',\n action: 'request-contract-evidence' };\n }\n\n return { status: 'evidence-map-ready',\n action: 'ask-owner-to-confirm-boundary' };\n}\nКод ограничен учебной проверкой объекта в памяти. Он не читает pull request, не запускает CI и не принимает решение о merge. Его ценность — в явных ветках. Каждая ветка показывает, какое условие отсутствует и что делать дальше. В production-автоматизации те же статусы потребуют отдельного контракта, тестов и владельца.
\nEvidence map не заменяет архитектурное решение, security assessment или эксплуатационную проверку. Он не вычисляет severity, не назначает SLA и не доказывает отсутствие дефекта. Для миграции данных понадобятся отдельные вопросы о совместимости версий, объёме записей и восстановлении. Для security-риска понадобятся trust boundary, правило входа и наблюдаемый сценарий злоупотребления. Нельзя переносить набор полей из одного риска в другой без проверки.
\nСтандарт также не делает review быстрым автоматически. Иногда карта потребителей дороже самого изменения. Это нормальная цена, если поле пересекает границу сервиса. Если изменение локально и контракт не меняется, достаточно меньшего набора evidence. Смысл стандарта не в максимальном числе проверок, а в соразмерности: риск определяет обязательные входы.
\nНе следует превращать каждое замечание в блокирующее. Комментарий о названии может улучшить читаемость, но не должен изображать угрозу совместимости. И наоборот, отсутствие доказательства по контракту нельзя закрывать фразой «потом посмотрим». Разделяйте обязательное условие и полезное предложение.
\nReview готов для передачи решения, когда выполнены четыре условия: цель изменения понятна; риск назван; каждый обязательный вход имеет проверяемый источник; отрицательный путь возвращает явное действие. Дополнительно проверьте, что итоговая формулировка соответствует данным. Если карта потребителей не полна, критерий не выполнен. Если evidence полон, это ещё не равно approval: это означает, что вопрос можно передать владельцу контракта с понятной границей.
\nПрактический тест можно выполнить на учебном объекте. Удалите consumer map — запись должна вернуть stop-insufficient-evidence. Замените проверку риска на style-only — запись должна вернуть stop-style-displaces-risk. Добавьте недопустимое слово approval — запись должна остановиться. Верните все поля и оставьте вывод ограниченным вопросом — запись должна пройти как готовая evidence map. Эти результаты проверяют механику примера, а не production-поведение.