Files

8 lines
22 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 44,
"slug": "editorial-2026-10-mechanism-code-review-standard",
"title": "Code review как механизм управления риском: evidence, stop и решение",
"excerpt": "Как отличить замечание о стиле от риска контракта, состояния или границы доверия, запросить проверяемое evidence и не выдать предположение за готовое решение.",
"contentHtml": "<p>В pull request меняется nullable-поле. Reviewer оставляет шесть комментариев о названиях и форматировании. Никто не спрашивает, какие клиенты читают новое значение, что получит старый клиент и как вернуть прежнюю форму. После слияния один потребитель начинает трактовать <code>null</code> как «скидки нет», а другой — как ошибку. Ошибка появляется не в строке с новым типом, а на границе контракта.</p>\n<p>Цена такого пропуска измеряется не количеством комментариев, а временем восстановления: команда разбирает несовместимый ответ, откатывает часть изменений и ищет владельца обратимости. При этом review выглядит аккуратно. Значит, проблема не в недостатке стилистических замечаний, а в том, что итоговый вывод оказался сильнее доступных фактов.</p>\n<h2>Вопрос review: что именно мы уже знаем</h2>\n<p>Надёжное review связывает четыре элемента: наблюдаемый симптом, класс риска, нужное evidence и допустимое действие. Evidence — проверяемое основание вывода: форма данных, список потребителей, переход состояния или правило входа. Если основание неполно, review останавливает вывод и называет недостающий факт. Это рабочее правило fail-closed: пробел не превращается в «скорее всего безопасно».</p>\n<p>Такой подход согласуется с двумя официальными ориентирами. GitHub описывает review как просмотр commits, изменённых файлов и diff перед решением approve или request changes. Руководство Google ставит выше личных предпочтений технические факты и данные, а целью review называет улучшение общего состояния кодовой базы. Эти документы не задают одну policy для всех команд, но дают проверяемую границу: комментарий должен помогать оценить изменение, а не только выражать вкус.</p>\n<h2>Класс риска определяет evidence</h2>\n<p><strong>Contract risk</strong> появляется, когда меняется форма данных или ожидание потребителя. Зафиксируйте старую и новую форму, перечислите категории потребителей и укажите, как вернуть прежний ответ. Если обратимость невозможна, это должно быть частью решения, а не обещанием в комментарии.</p>\n<p><strong>Operational risk</strong> появляется, когда меняется поведение во времени. Запишите состояние до ветки, событие, состояние после неё и результат повтора. Слова <code>retry</code> и <code>timeout</code> ничего не доказывают сами по себе: нужно показать, повторяется ли побочный эффект и кто увидит отказ.</p>\n<p><strong>Security risk</strong> появляется на границе доверия. Назовите доверенную сторону, правило входа и последствие нарушения. Непривычный diff ещё не означает уязвимость. Но отсутствие карты доверия не позволяет утверждать, что проверка входа защищает систему.</p>\n<figure><img src=\"/assets/editorial/2026/code-review-standard-2026-risk-escalation-gates.svg\" alt=\"Три этапа code review: определить риск, проверить evidence и остановиться при неполных данных перед передачей вопроса владельцу.\" loading=\"lazy\" /><figcaption>Порядок вывода: определить границу риска, собрать соответствующие факты, остановиться при пробеле и только затем передать точный вопрос владельцу.</figcaption></figure>\n<p>Gate не обязан выдавать approve или reject. Его задача уже выполнена, если неполный input не породил ложное решение. При полном наборе фактов reviewer всё ещё не доказывает работоспособность всей системы. Он получает право сформулировать узкий вопрос: «проверьте совместимость этих потребителей с новой формой».</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>В обсуждении много style-комментариев, но нет вопроса о данных</td><td>Не названа граница контракта</td><td>Сравнить старую и новую форму, найти категории потребителей</td><td>Остановить вывод и запросить карту совместимости</td></tr><tr><td>Есть обработчик ошибки, но непонятно, что будет при повторе</td><td>Не описан переход состояния</td><td>Записать состояние до события, после события и эффект повтора</td><td>Задать operational-вопрос владельцу состояния</td></tr><tr><td>Валидатор принимает вход, но источник доверия не определён</td><td>Смешаны проверка значения и security boundary</td><td>Назвать trust boundary, правило входа и последствие обхода</td><td>Передать точный вопрос владельцу безопасности</td></tr><tr><td>Комментарий говорит «безопасно» после одного теста</td><td>Вывод шире проверенного свойства</td><td>Сопоставить утверждение с входами, ветками и потребителями теста</td><td>Заменить вердикт на ограниченный результат</td></tr></tbody></table></div>\n<p>У таблицы есть практическая граница: она не ранжирует severity и не определяет владельца автоматически. Её задача — не потерять первый диагностический шаг. Если в одной строке одновременно появляются три разных риска, разделите их: иначе evidence станет слишком общим и stop снова превратится в «проверьте всё».</p>\n<h2>Воспроизводимый пример stop</h2>\n<p>Ниже — самостоятельная функция для проверки полноты входной карты. Она не читает pull request, репозиторий, CI, сеть или production. Запустите её в Node.js, передав изменение схемы, карту потребителей и описание возврата. Код проверяет только наличие трёх полей; он не делает вывод о совместимости клиентов.</p>\n<pre><code>const required = ['schemaDelta', 'consumerMap', 'rollbackNote'];\n\nfunction assessContractEvidence(input) {\n const missing = required.filter((name) =&gt; !input[name]);\n\n if (missing.length &gt; 0) {\n return {\n status: 'stop-insufficient-evidence',\n missing,\n action: 'request-only-named-evidence'\n };\n }\n\n return {\n status: 'contract-question-ready',\n action: 'ask-owner-to-check-compatibility'\n };\n}\n\nconsole.log(assessContractEvidence({\n schemaDelta: 'price: number -&gt; number | null',\n rollbackNote: 'restore previous response before consumer rollout'\n}));\n// { status: 'stop-insufficient-evidence',\n// missing: [ 'consumerMap' ],\n// action: 'request-only-named-evidence' }</code></pre>\n<p>Результат содержит только имя отсутствующего evidence. Функция не обращается к клиентам и не сообщает, что совместимость нарушена. Если добавить <code>consumerMap</code>, статус изменится на <code>contract-question-ready</code>. Это тоже не approval: можно задать владельцу контракта конкретный вопрос, но ответ ещё должен опираться на исходный код, схему или контрактный тест.</p>\n<p>Три поля в примере — проектные. <code>schemaDelta</code> означает точную старую и новую форму, <code>consumerMap</code> — границу поиска и категории потребителей, <code>rollbackNote</code> — старую форму, порядок возврата и условия обратимости. В другом проекте минимальный набор будет иным. Важно сохранить правило: каждое требуемое поле должно быть связано с конкретным риском и способом проверки.</p>\n<h2>Как проверить реальный pull request</h2>\n<p>Начните с цели изменения и списка затронутых файлов. В документации GitHub отдельный просмотр файлов, комментарии на конкретных изменениях и отметка Viewed помогают не потерять часть diff. Это полезная операционная последовательность, но отметка Viewed не доказывает корректность кода. После неё всё равно нужна проверка свойства, ради которого меняли систему.</p>\n<ol><li>Сформулируйте симптом в наблюдаемой форме: какое поле, состояние, вход или побочный эффект изменился.</li><li>Снимите старое и новое поведение из схемы, теста, логов или исходного кода; не подменяйте их пересказом описания PR.</li><li>Составьте карту потребителей: UI, API-клиенты, фоновые задачи, миграции, операционные скрипты и внешние интеграции.</li><li>Для каждого потребителя запишите ожидаемый ответ на <code>null</code>, отказ, повтор или недопустимый вход.</li><li>Проверьте отрицательный путь отдельным тестом или воспроизводимой командой: ошибка должна менять только ожидаемое состояние и не дублировать побочный эффект.</li><li>Сверьте утверждение с evidence. Если не хватает одной позиции, напишите stop с её точным именем.</li><li>Если риск и evidence определены, передайте владельцу один вопрос с границей вывода и ожидаемым результатом.</li><li>В итоговом комментарии разделите обязательные изменения и замечания типа polish; личное предпочтение не должно блокировать поведенческий вывод.</li></ol>\n<h2>Почему стиль вытесняет поведение</h2>\n<p>Стиль легко обсуждать: строка видна, правило знакомо, исправление локально. Контракт требует контекста. Поэтому style-комментарий не плох сам по себе. Ошибка возникает, когда он закрывает обсуждение изменения поведения. Обязательное правило форматирования исправьте локально, а риск контракта вынесите отдельным блоком с доказательством и владельцем.</p>\n<p>Наличие теста также не равно проверке нужного свойства. Unit-тест может подтвердить, что функция вернула <code>null</code>, но не показывает, как значение трактуют старые потребители. Тест ветки ошибки может пройти, хотя повтор операции создаёт дубль, если побочный эффект выполняется до записи статуса. Руководство Google отдельно предлагает проверять edge cases и спрашивать, упадёт ли тест при поломке кода. Это хороший фильтр для фразы «тесты зелёные».</p>\n<p>Вместо общего комментария оставьте наблюдаемую формулировку: «в ответе поле стало nullable; для клиента A не найдено поведение при <code>null</code>». Такой комментарий содержит изменение, missing evidence и ожидаемого владельца. Он полезнее утверждения «API небезопасен», если проверка ещё не показала нарушение.</p>\n<h2>Stop и escalation — разные действия</h2>\n<p><strong>Stop</strong> означает, что данных недостаточно даже для точного следующего вопроса. Если неизвестен класс риска, сначала опишите границу изменения. Если нет карты потребителей, запросите только её. Не пишите «проверьте всё»: такой запрос нельзя проверить и нельзя завершить.</p>\n<p><strong>Escalation</strong> означает, что риск и нужное evidence названы, но решение принадлежит другой роли. Вопрос о форме ответа передают владельцу контракта, вопрос о повторе побочного эффекта — владельцу состояния или эксплуатации, вопрос о границе доверия — владельцу безопасности. Передача должна содержать риск, факты, точный вопрос и ограничение вывода. Она не должна приписывать получателю готовый диагноз.</p>\n<p>Корректные формулировки звучат слабее, но дают следующий шаг: «нужно проверить совместимость потребителей с новой nullable-формой», «нужно уточнить повтор операции после timeout», «нужно привлечь владельца trust boundary». Пока нет проверки, нельзя писать «все клиенты совместимы», «retry безопасен» или «уязвимость найдена».</p>\n<h2>Ограничения применимости</h2>\n<p>Модель не считает severity, не назначает SLA и не заменяет security-аудит, контрактные тесты, нагрузочные проверки или миграционный план. Она не доказывает отсутствие дефекта. Она ограничивает силу комментария теми фактами, которые реально собраны.</p>\n<p>Для финансовой операции в карту добавьте идемпотентность, аудит и правила сверки. Для публичного API — версию, период совместимости и план удаления старого поля. Для персональных данных — источник согласия, срок хранения и права доступа. Для конкурентного кода — интерливинг, блокировки и наблюдаемое состояние. Эти позиции нужны только там, где они принадлежат затронутой границе.</p>\n<p>Пример с <code>assessContractEvidence</code> намеренно мал. Он не моделирует распределённую транзакцию, не запускает миграцию и не возвращает production-метрики. Его проверяемое свойство уже: при отсутствии <code>consumerMap</code> функция возвращает stop и не объявляет совместимость доказанной. Переносить этот статус на реальные клиенты без отдельной проверки нельзя.</p>\n<h2>Критерий готовности</h2>\n<p>Review готово, когда читатель без догадок отвечает на четыре вопроса: какой симптом привёл к проверке, какая граница риска затронута, какое evidence подтверждает следующий вопрос и какое утверждение запрещено делать. Для неполного набора виден точный stop. Для полного набора есть владелец вопроса, отрицательный путь и критерий результата. Если остаются «выглядит хорошо», «тесты зелёные» или «потом проверим», поведенческий вывод ещё не закрыт.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://docs.github.com/en/pull-requests/how-tos/review-pull-requests/reviewing-proposed-changes-in-a-pull-request\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: Reviewing proposed changes in a pull request</a> — описывает просмотр commits, изменённых файлов и diff, комментарии к конкретным изменениям, отметку Viewed и варианты Comment, Approve или Request changes. Источник не определяет классы риска из этой статьи.</li><li><a href=\"https://google.github.io/eng-practices/review/reviewer/standard.html\" target=\"_blank\" rel=\"noopener noreferrer\">Google Engineering Practices: The Standard of Code Review</a> — связывает review с улучшением общего состояния кодовой базы, требует опираться на технические факты и допускает баланс качества с продвижением изменений. Это руководство Google, а не обязательная policy для каждой команды.</li><li><a href=\"https://google.github.io/eng-practices/review/reviewer/looking-for.html\" target=\"_blank\" rel=\"noopener noreferrer\">Google Engineering Practices: What to look for in a code review</a> — перечисляет design, functionality, edge cases, complexity, tests, naming, comments, style и documentation как области проверки. Источник не подтверждает результаты конкретного review.</li></ul>"
}