8 lines
19 KiB
JSON
8 lines
19 KiB
JSON
{
|
||
"index": 43,
|
||
"slug": "editorial-2026-10-field-code-review-standard",
|
||
"title": "Code review: как не пропустить риск изменения контракта",
|
||
"excerpt": "Форматирование занимает строки в комментариях, а необратимое изменение контракта остаётся без проверки. Разбираем порядок review, который связывает симптом, evidence, отрицательный путь и решение.",
|
||
"contentHtml": "<p>В pull request меняют поле ответа с обязательного на nullable. В комментариях спорят о названии функции, порядке импортов и длине строки. Через неделю старый клиент падает на пустом значении. Ошибка возникла не в синтаксисе. Review проверил видимый diff, но не проверил границу контракта. Цена такого пропуска — аварийный откат, срочный выпуск совместимости и потеря времени у команды, которая теперь ищет всех потребителей вслепую.</p>\n<p>Тезис простой: code review должен связывать каждый существенный риск с проверяемым evidence. Если изменение меняет форму данных, одного чтения строк недостаточно. Нужно назвать потребителей, переходы состояния и путь возврата. Если evidence не хватает, reviewer формулирует точный вопрос и останавливает сильный вывод. Он не заменяет пробел догадкой и не маскирует его стилевым комментарием.</p>\n<h2>Сначала отделите симптом от причины</h2>\n<p>Симптом обычно виден в обсуждении: много мелких замечаний, спор о вкусе, длинный список предложений без одного вопроса о поведении системы. Это не доказывает плохой review. Но это сигнал проверить, не вытеснил ли стиль риск. Причина часто лежит за пределами изменённого файла: у поля есть другой consumer, миграция не обратима, а тест покрывает только новый путь.</p>\n<p>Начните с вопроса: что изменится для пользователя или соседнего сервиса, если этот diff попадёт в основную ветку? Ответ должен быть конкретным. «Станет современнее» не подходит. «Клиент, который не различает null и отсутствие поля, получит другой результат» — подходит. Следующий вопрос: каким артефактом это можно проверить? Это может быть schema delta, карта потребителей, тест на старую форму или явная инструкция отката. Список должен быть конечным.</p>\n<h2>Механизм evidence map</h2>\n<p>Удобно хранить review как короткую связку из пяти полей: change, risk, evidence, status и next action. Change называет один предмет. Risk описывает тип последствий, а не эмоциональную оценку. Evidence перечисляет входы, которыми можно проверить риск. Status показывает границу текущего вывода. Next action говорит, что должен сделать следующий владелец.</p>\n<pre><code>change: 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</code></pre>\n<p>Имена в примере учебные. Они не ссылаются на настоящий репозиторий, pull request или production-систему. Их задача — показать форму записи. В реальном review вместо них нужны ссылки на существующие артефакты и владелец каждого из них.</p>\n<p>Эта модель снижает силу вывода до уровня входных данных. Полная карта потребителей позволяет задать вопрос о совместимости. Она не доказывает, что каждый клиент уже обновлён. Schema delta показывает изменение формы. Она не доказывает, что миграция обратима. Rollback note описывает возможный путь возврата. Он не доказывает, что команда успеет выполнить его в аварии.</p>\n<h2>Пример: nullable-поле и скрытый consumer</h2>\n<p>Представьте учебный API ответа со скидкой. Было <code>discount: number</code>, стало <code>discount: number | null</code>. Сервер может собрать ответ, а новый тест может пройти. Но старый клиент способен сразу передать значение в арифметику или отрисовать его без ветки для null. Поэтому строка изменения ещё не является достаточным evidence.</p>\n<pre><code>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}</code></pre>\n<p>В этом фрагменте проверяется только локальное правило функции. Он не проверяет всех клиентов и не показывает результат выпуска. Чтобы review был содержательным, нужно найти границу потребления: кто декодирует ответ, какие значения разрешает его схема, что делает старый код и как тестируется несовместимая форма. Если карты нет, правильный комментарий звучит так: «Нужен список потребителей поля и их поведение при null. Без него нельзя оценить охват изменения». Это вопрос, а не вердикт о качестве автора.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<div class=\"table-scroll\"><table><caption>Как переводить наблюдение в следующий проверяемый шаг</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Комментарии заполнены форматированием</td><td>Риск поведения не назван</td><td>Сверить diff с целью и контрактом</td><td>Снять style-only комментарии и задать один вопрос о последствиях</td></tr><tr><td>Поле стало nullable</td><td>Не видны все consumers</td><td>Проверить schema delta и карту потребителей</td><td>Запросить конкретный список клиентов и обработку null</td></tr><tr><td>Есть слово rollback</td><td>Не описано, что возвращается</td><td>Сопоставить старую форму и переход состояния</td><td>Попросить шаг возврата и условие его применимости</td></tr><tr><td>Тест проходит только на новом ответе</td><td>Отрицательный путь отсутствует</td><td>Подать старую форму и null</td><td>Добавить проверку отказа или безопасного значения</td></tr><tr><td>Автор просит approve при неполном input</td><td>Вывод сильнее evidence</td><td>Проверить обязательные поля risk-класса</td><td>Остановить review с перечнем недостающих данных</td></tr></tbody></table></div>\n<h2>Как выглядит отрицательный путь</h2>\n<p>Надёжный стандарт должен объяснять остановку так же ясно, как положительный путь. Если отсутствует consumer map, статус — «недостаточно evidence», а действие — запросить только карту. Не нужно добавлять «вероятно безопасно» или искать потребителей по памяти. Если reviewer видит только изменение стиля, а риск относится к контракту, стилевой комментарий не закрывает проверку. Если risk class неизвестен, сначала нужно назвать его границы.</p>\n<p>Есть и другой стоп-сигнал: все обязательные артефакты перечислены, но итоговая фраза говорит «approve and merge». Полный набор входов не превращает учебную карточку в разрешение на слияние. В настоящем процессе approval зависит от полномочий, политики репозитория и результата остальных проверок. В записи review лучше разделять «evidence достаточно для следующего вопроса» и «изменение готово к merge».</p>\n<pre><code>function 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}</code></pre>\n<p>Код ограничен учебной проверкой объекта в памяти. Он не читает pull request, не запускает CI и не принимает решение о merge. Его ценность — в явных ветках. Каждая ветка показывает, какое условие отсутствует и что делать дальше. В production-автоматизации те же статусы потребуют отдельного контракта, тестов и владельца.</p>\n<figure><img src=\"/assets/editorial/2026/code-review-standard-2026-review-handoff-loop.svg\" alt=\"Петля code review: риск связывается с evidence, неполные данные останавливают вывод, полный набор передаёт вопрос владельцу контракта\" loading=\"lazy\" /><figcaption>Проверка должна замыкаться на evidence: неполный вход возвращает точный вопрос, а не уверенный вердикт.</figcaption></figure>\n<h2>Порядок проверки</h2>\n<ol><li>Назовите цель изменения одним предложением. Укажите, какая форма данных, граница доступа или переход состояния меняется.</li><li>Выберите один риск-класс. Для nullable-поля это совместимость контракта, а не абстрактное «качество кода».</li><li>Составьте короткий список обязательного evidence: schema delta, потребители, отрицательный путь и способ возврата, если он нужен.</li><li>Проверьте каждый пункт по конкретному артефакту. Если ссылки нет или артефакт не отвечает на вопрос, пометьте пункт как отсутствующий.</li><li>Сначала прогоните отрицательные ветки: нет карты потребителей, выбран только стиль, не назван risk class, вывод просит approval.</li><li>Сформулируйте действие с одним владельцем и одним недостающим входом. Не отправляйте список предположений.</li><li>После получения evidence повторите проверку границы. Убедитесь, что вывод не стал сильнее данных и что новый тест покрывает отказной путь.</li></ol>\n<h2>Ограничения</h2>\n<p>Evidence map не заменяет архитектурное решение, security assessment или эксплуатационную проверку. Он не вычисляет severity, не назначает SLA и не доказывает отсутствие дефекта. Для миграции данных понадобятся отдельные вопросы о совместимости версий, объёме записей и восстановлении. Для security-риска понадобятся trust boundary, правило входа и наблюдаемый сценарий злоупотребления. Нельзя переносить набор полей из одного риска в другой без проверки.</p>\n<p>Стандарт также не делает review быстрым автоматически. Иногда карта потребителей дороже самого изменения. Это нормальная цена, если поле пересекает границу сервиса. Если изменение локально и контракт не меняется, достаточно меньшего набора evidence. Смысл стандарта не в максимальном числе проверок, а в соразмерности: риск определяет обязательные входы.</p>\n<p>Не следует превращать каждое замечание в блокирующее. Комментарий о названии может улучшить читаемость, но не должен изображать угрозу совместимости. И наоборот, отсутствие доказательства по контракту нельзя закрывать фразой «потом посмотрим». Разделяйте обязательное условие и полезное предложение.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Review готов для передачи решения, когда выполнены четыре условия: цель изменения понятна; риск назван; каждый обязательный вход имеет проверяемый источник; отрицательный путь возвращает явное действие. Дополнительно проверьте, что итоговая формулировка соответствует данным. Если карта потребителей не полна, критерий не выполнен. Если evidence полон, это ещё не равно approval: это означает, что вопрос можно передать владельцу контракта с понятной границей.</p>\n<p>Практический тест можно выполнить на учебном объекте. Удалите consumer map — запись должна вернуть <code>stop-insufficient-evidence</code>. Замените проверку риска на style-only — запись должна вернуть <code>stop-style-displaces-risk</code>. Добавьте недопустимое слово approval — запись должна остановиться. Верните все поля и оставьте вывод ограниченным вопросом — запись должна пройти как готовая evidence map. Эти результаты проверяют механику примера, а не production-поведение.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://docs.github.com/en/pull-requests/concepts/giving-reviews\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: Giving reviews</a> — официальное описание комментариев, approve и request changes. Источник описывает возможности GitHub, но не доказывает качество конкретного review.</li><li><a href=\"https://doi.org/10.6028/NIST.SP.800-218\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-218: Secure Software Development Framework</a> — нормативная рамка практик безопасной разработки. Она не задаёт этот шаблон review и не подтверждает отсутствие уязвимости.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc2119\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 2119: Key words for use in RFCs to Indicate Requirement Levels</a> — источник для осторожного употребления нормативных слов. Он не назначает severity и не заменяет решение владельца.</li></ul>"
|
||
}
|