Files
progcode/editorial/agent-rewrites/044.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
19 KiB
JSON
Raw 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 могло выглядеть аккуратно. Проблема не в том, что reviewer не заметил все дефекты. Проблема в том, что вывод оказался сильнее доступных фактов.</p>\n<h2>Тезис: сначала ограничьте вывод</h2>\n<p>Надёжное review связывает четыре элемента: наблюдаемый симптом, класс риска, нужное evidence и допустимое действие. Evidence — это проверяемое основание вывода: форма данных, список потребителей, переход состояния или правило входа. Если основание неполно, review останавливает вывод и называет недостающий факт. Такой режим называют fail-closed: пробел не превращается в «скорее всего безопасно».</p>\n<p>Эта схема не оценивает reviewer и не делает из checklist универсальную policy. Она помогает выбрать следующий вопрос. Изменение формы ответа требует проверить контракт. Новая ветка ошибки требует проверить состояние до и после неё. Проверка входа требует определить границу доверия и возможное злоупотребление. Один комментарий о стиле не закрывает ни одну из этих границ.</p>\n<h2>Механизм: риск выбирает доказательство</h2>\n<p>Contract risk возникает, когда меняется форма данных или ожидание потребителя. Назовите старую и новую форму. Затем перечислите категории потребителей. После этого опишите возврат к старой форме или честно укажите, что возврат невозможен.</p>\n<p>Operational risk возникает, когда меняется поведение во времени. Запишите состояние до ветки, событие, состояние после неё и результат повтора. Слова «retry» и «timeout» сами по себе ничего не доказывают. Нужно показать, повторяется ли побочный эффект и кто увидит отказ.</p>\n<p>Security risk возникает на границе доверия. Назовите доверенную сторону, правило входа и последствие нарушения. Непривычный diff ещё не означает уязвимость. Но отсутствие границы доверия не позволяет утверждать, что проверка входа защищает систему.</p>\n<figure><img src=\"/assets/editorial/2026/code-review-standard-2026-risk-escalation-gates.svg\" alt=\"Три gate для code review: неизвестный риск останавливает проверку, неполное evidence возвращает запрос, полный набор фактов разрешает ограниченный hand-off.\" 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>Остановить вывод и запросить contract evidence</td></tr><tr><td>Есть обработчик ошибки, но непонятно, что будет при повторе</td><td>Не описан переход состояния</td><td>Записать state до ветки, событие, state после и эффект повтора</td><td>Сформулировать operational question</td></tr><tr><td>Валидатор принимает вход, но доверие к источнику не определено</td><td>Смешаны проверка значения и security boundary</td><td>Назвать trust boundary, input rule и abuse consequence</td><td>Передать вопрос владельцу безопасности</td></tr><tr><td>Комментарий говорит «безопасно» после одного теста</td><td>Вывод шире evidence</td><td>Сверить утверждение с тем, что реально проверил тест</td><td>Заменить вердикт на ограниченный результат</td></tr></tbody></table></div>\n<h2>Учебный пример: остановка при неполной карте потребителей</h2>\n<p>Ниже показана учебная ветка для изменения контракта. Она не читает pull request, репозиторий, CI, сеть или production. Функция получает обычный объект и возвращает статус. Такой пример объясняет механизм stop, но не проверяет совместимость реальных клиентов.</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// missing: ['consumerMap']</code></pre>\n<p>Вызов возвращает только имя отсутствующего evidence. Он не делает запрос к клиентам и не сообщает, что совместимость нарушена. Если добавить <code>consumerMap</code>, статус изменится на <code>contract-question-ready</code>. Это тоже не approval. Он лишь разрешает задать владельцу контракта конкретный вопрос.</p>\n<p>В реальном review названия полей должны описывать факты проекта. <code>schemaDelta</code> — это не слово «изменился API», а точная старая и новая форма. <code>consumerMap</code> — не список случайных сервисов, а граница поиска и категории потребителей. <code>rollbackNote</code> — не обещание отката, а описание старой формы, порядка возврата и условий, при которых возврат возможен.</p>\n<h2>Почему стиль часто вытесняет риск</h2>\n<p>Стиль легко обсуждать: строка видна, правило знакомо, исправление локально. Контракт требует контекста. Поэтому style-комментарий не плох сам по себе. Ошибка возникает, когда он закрывает обсуждение изменения поведения. Разделяйте уровни: обязательное правило форматирования исправьте локально, а риск контракта вынесите отдельным блоком с доказательством и владельцем.</p>\n<p>То же относится к тесту. Наличие теста не равно проверке нужного свойства. Unit-тест может подтвердить, что функция вернула <code>null</code>. Он не показывает, как это значение трактуют старые потребители. Тест перехода состояния может подтвердить ветку ошибки. Он не доказывает, что повтор не создаёт дубль, если побочный эффект выполняется до записи статуса.</p>\n<h2>Stop и escalation — разные действия</h2>\n<p>Stop означает, что данных недостаточно даже для точного следующего вопроса. Если неизвестен класс риска, сначала опишите границу изменения. Если для contract risk нет карты потребителей, запросите только её. Не добавляйте «проверьте всё» — такой запрос нельзя проверить и нельзя завершить.</p>\n<p>Escalation означает, что риск и нужное evidence названы, но решение принадлежит другой роли. Вопрос о форме ответа передают владельцу контракта. Вопрос о повторе побочного эффекта — владельцу состояния или эксплуатации. Вопрос о границе доверия — владельцу безопасности. Передача должна содержать риск, факты, точный вопрос и ограничение вывода. Она не должна приписывать получателю готовый диагноз.</p>\n<p>Слабый вывод здесь полезнее громкого. «Нужно проверить совместимость потребителей с новой nullable-формой» честнее, чем «все клиенты совместимы». «Нужно уточнить повтор операции после timeout» честнее, чем «retry безопасен». «Нужно привлечь владельца trust boundary» честнее, чем «уязвимость найдена».</p>\n<h2>Порядок действий</h2>\n<ol><li>Опишите наблюдаемый симптом: что изменилось в diff и какое поведение может увидеть пользователь, потребитель или оператор.</li><li>Назовите одну границу риска: контракт, переход состояния или доверие к входу.</li><li>Сопоставьте границе минимальное evidence и отделите факт от предположения.</li><li>Проверьте каждую позицию по исходному коду, тесту, схеме или документу; не заменяйте её общим «выглядит нормально».</li><li>Если позиция отсутствует, верните stop с точным именем missing evidence.</li><li>Если набор полон, сформулируйте ограниченный вопрос и передайте его владельцу границы.</li><li>Отдельно проверьте отрицательный путь: повтор, отказ, старый потребитель, недопустимый вход или невозможность возврата.</li><li>Закройте review только после проверки того свойства, ради которого меняли код; style-заметки не выдавайте за доказательство поведения.</li></ol>\n<h2>Ограничения</h2>\n<p>Эта модель не считает severity, не назначает SLA и не заменяет security-аудит, контрактные тесты, нагрузочные проверки или миграционный план. Она не доказывает отсутствие дефекта. Она ограничивает силу комментария доступными фактами.</p>\n<p>Одна матрица не покрывает весь домен. Для финансовой операции могут потребоваться идемпотентность и аудит. Для публичного API — версия и период совместимости. Для персональных данных — срок хранения и права доступа. Добавляйте такие строки, когда они принадлежат конкретной границе. Не превращайте review в ритуал, где каждый change получает одинаковый пакет документов.</p>\n<p>Учебный код выше намеренно мал. Он не моделирует распределённую транзакцию, не запускает миграцию и не возвращает 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 перед решением по pull request. Источник не определяет классы риска из этой статьи.</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, complexity, tests, naming, comments, style и documentation как области проверки. Источник не подтверждает результаты конкретного review.</li></ul>"
}