Files
progcode/editorial/agent-rewrites/289.json
T

8 lines
22 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": 289,
"slug": "editorial-2019-12-field-code-ownership",
"title": "Когда у ошибки четыре владельца: как разбирать код на стыке модулей",
"excerpt": "Неизвестный статус проходит через gateway и checkout, а команда ищет автора последней строки. Разделяем историю кода, владельца решения, маршрут review и проверку после merge.",
"contentHtml": "<p>Checkout получает от gateway значение <code>unknown</code>, но показывает пользователю успешную оплату. В логах есть ответ интеграции, в коде есть ветка по умолчанию, а в задаче первым делом ищут автора строки через <code>git blame</code>. Ошибка превращается в спор: человек из checkout менял parser, команда gateway владеет API, а после merge никто не обязан проверить результат.</p>\n<p>Цена такого смешения — не только задержка. Неизвестное состояние может попасть в успешный сценарий, повторить неверное действие и породить второй дефект после исправления. Команда тратит время на поиск виноватого, но не фиксирует, кто принимает решение о контракте и кто проверяет его на рабочем пути.</p>\n<p>Тезис статьи простой: у одного дефекта могут быть разные владельцы. Исторический автор помогает восстановить контекст. Владелец пути кода знает, где менять поведение. Владелец решения определяет смысл статуса. Reviewer проверяет конкретный риск, а отдельный исполнитель подтверждает результат после merge. Эти роли могут совпасть, но их нельзя считать совпавшими без проверки.</p>\n<h2>Что именно показывает каждый след</h2>\n<p><code>git blame</code> отвечает на исторический вопрос: какая revision последней изменила строку и кто её изменил. Это полезная точка входа. Автор мог сделать перенос, форматирование или механический рефакторинг. Из одной строки нельзя вывести, кто сегодня отвечает за смысл статуса.</p>\n<p><code>git log --follow</code> добавляет контекст: какие изменения проходили через файл и где лежала прежняя версия. История помогает найти обсуждение, тест или документ контракта. Она не назначает текущего владельца. После переноса кода автор строки и эксперт по интеграции часто расходятся.</p>\n<pre><code>git blame -L 42,58 -- src/checkout/status.ts\ngit log --follow -- src/checkout/status.ts\ngit log -S&quot;viewByStatus&quot; -- src/checkout/status.ts</code></pre>\n<p>Пути и номера строк в примере условны. Первая команда ограничивает blame нужным диапазоном, вторая следует за переименованием файла, а третья ищет изменения, в которых встречалась строка <code>viewByStatus</code>. История сужает поиск, но не назначает текущего владельца и не заменяет чтение контракта.</p>\n<p>В GitHub файл <code>CODEOWNERS</code> может автоматически запросить review по совпавшему пути. Для pull request используется версия файла из base branch. Если совпало несколько правил, последнее имеет приоритет. Это автоматизирует маршрут reviewer, но не доказывает, что человек согласовал семантику API или проверил поведение после merge.</p>\n<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><code>git blame</code></td><td>Кто последним изменил строку?</td><td>Кто владеет текущим правилом?</td><td>Прочитать diff и связанные изменения</td></tr><tr><td>Путь кода</td><td>Где находится поведение и соседние переходы?</td><td>Что новое значение означает для продукта?</td><td>Найти тест, контракт и владельца модуля</td></tr><tr><td><code>CODEOWNERS</code></td><td>Кого платформа запросит на review?</td><td>Что reviewer действительно подтвердил?</td><td>Проверить base branch, правило и доступ команды</td></tr><tr><td>Review и follow-up</td><td>Что проверили до merge и кто проверит результат?</td><td>Что ошибка исчезла сама по себе?</td><td>Записать решение и наблюдаемый критерий</td></tr></tbody></table>\n<p>Таблица задаёт границы ответственности. Она не требует создавать четыре должности. Один разработчик может закрыть все роли в маленьком модуле. На стыке gateway и checkout роли расходятся чаще, поэтому их нужно назвать в задаче или описании pull request.</p>\n<figure><img src=\"/assets/editorial/2019/code-ownership-simulated-timeline-2019.svg\" alt=\"Учебная шкала от неизвестного статуса через поиск истории и review к проверке после merge\" loading=\"lazy\"><figcaption>Учебная схема: исторический автор, владелец решения, reviewer и исполнитель проверки отвечают за разные вопросы.</figcaption></figure>\n<h2>Учебный сценарий на стыке gateway и checkout</h2>\n<p>Ниже приведён искусственный пример. Он показывает маршрут расследования и не описывает реальный инцидент, пользователей, команду или production-результат. Предположим, gateway возвращает JSON с полем <code>status</code>, а checkout переводит его в состояние экрана.</p>\n<pre><code>const viewByStatus = {\n paid: 'success',\n pending: 'waiting',\n failed: 'error',\n};\n\nexport function getView(status) {\n return viewByStatus[status] || 'success';\n}</code></pre>\n<p>Ошибка находится в значении по умолчанию. Если gateway добавит <code>review</code>, checkout покажет успех. Даже если автор этой строки давно ушёл из команды, вопрос остаётся техническим: допустимо ли считать неизвестный статус успешным? Владелец контракта должен ответить «нет» или обосновать другое правило.</p>\n<p>Безопаснее сделать неизвестное значение отдельным состоянием и сохранить его для диагностики:</p>\n<pre><code>const viewByStatus = {\n paid: 'success',\n pending: 'waiting',\n failed: 'error',\n};\n\nexport function getView(status) {\n return viewByStatus[status] || 'unknown';\n}\n\nexport function canConfirmPayment(status) {\n return status === 'paid';\n}</code></pre>\n<p>В этом учебном коде <code>unknown</code> не разрешает подтверждение оплаты. Названия состояний, способ отображения и политика повторного запроса зависят от конкретного продукта. Нельзя переносить их в рабочую систему без проверки контракта. Но граница решения ясна: только явное значение <code>paid</code> открывает успешное действие.</p>\n<p>Проверка должна покрыть и отрицательный путь:</p>\n<pre><code>test('does not treat an unknown status as paid', () =&gt; {\n expect(getView('review')).toBe('unknown');\n expect(canConfirmPayment('review')).toBe(false);\n});</code></pre>\n<p>Этот тест не доказывает, что gateway всегда присылает корректные данные. Он доказывает только выбранный локальный контракт: неизвестное значение не становится успешным. Отдельно нужно решить, где логировать исходный статус, как показать пользователю безопасное состояние и кто проверит экран после merge.</p>\n<h2>Разбор симптома</h2>\n<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>Сравнить <code>git blame</code> с контрактом и текущей командой модуля</td><td>Назвать отдельно owner решения и owner пути</td></tr><tr><td>Reviewer не получил запрос</td><td>Путь не совпал или <code>CODEOWNERS</code> изменён только в feature branch</td><td>Проверить файл в base branch, порядок правил и доступ команды</td><td>Исправить маршрут или запросить review вручную</td></tr><tr><td>Review зелёный, но статус всё ещё неверен</td><td>Reviewer проверил форму diff, а не смысл контракта</td><td>Найти в review конкретный вопрос о <code>unknown</code> и тест отрицательного случая</td><td>Добавить проверяемое решение и повторить review</td></tr><tr><td>После merge нет ответа о результате</td><td>Follow-up не назначили до изменения</td><td>Найти сигнал, срок и исполнителя проверки</td><td>Создать отдельное действие; не закрывать технический след одним merge</td></tr><tr><td>Неизвестный статус открывает оплату</td><td>Ветка по умолчанию разрешает success</td><td>Подменить вход в тесте на новое значение</td><td>Сделать allowlist для успешного состояния и заблокировать отрицательный путь</td></tr></tbody></table>\n<p>Проверка должна отделять факт от гипотезы. Если <code>CODEOWNERS</code> не отправил запрос, сначала проверяют расположение файла и правила. Если тест не проходит, сначала фиксируют вход и ожидаемый результат. Имя последнего автора не закрывает ни один из этих вопросов.</p>\n<h2>Как оформить минимальный контракт</h2>\n<p>Для учебного примера достаточно записать четыре поля в задаче:</p>\n<pre><code>symptom: checkout shows success for an unknown gateway status\ndecision owner: gateway-contract team\ncode path owner: checkout team\nreview questions:\n - may an unknown status enable confirmation?\n - does the fallback preserve a safe user state?\nfollow-up owner: checkout on staging</code></pre>\n<p>Имена здесь условные. В настоящем проекте нужно указать реальные команды, путь, тест и канал проверки. Поле <code>decision owner</code> означает не «кто будет чинить», а «кто может подтвердить смысл допустимых состояний». Поле <code>follow-up owner</code> означает не гарантию успеха, а конкретное действие после merge.</p>\n<p>Для такого дерева путей учебная конфигурация может выглядеть так:</p>\n<pre><code>* @example/platform\n/web/checkout/ @example/checkout\n/web/checkout/gateway/ @example/payments\n/docs/checkout-contract.md @example/payments @example/checkout\n/.github/CODEOWNERS @example/repository-admins</code></pre>\n<p>Псевдонимы вымышлены. Смысл примера — показать порядок: узкий путь gateway расположен после общего пути checkout. Если один pattern должен назначить несколько owners, на GitHub их записывают в одной строке. Правило не заменяет проверку доступа и не переносит владельца решения из документа в платформу автоматически.</p>\n<h2>Порядок действий</h2>\n<ol><li>Записать точный симптом: вход gateway, значение статуса, экран и неверное действие. Не начинать с имени сотрудника.</li><li>Ограничить расследование нужным путем и строками. Выполнить <code>git blame -L</code>, затем прочитать diff и историю связанных файлов через <code>git log</code>.</li><li>Найти контракт, тест или документ, где определены допустимые статусы. Если правило не сформулировано, сначала назначить владельца решения.</li><li>Назвать отдельно владельца решения, владельца пути, reviewer и follow-up. Разрешить одному человеку закрыть несколько ролей, но записать это явно.</li><li>Проверить <code>CODEOWNERS</code> в base branch. Убедиться, что совпадает нужный путь, последнее правило имеет ожидаемый приоритет, а команда имеет требуемый доступ.</li><li>Добавить тест на прошлый сбой и отрицательный тест для неизвестного значения. Успешный путь должен проходить только при явном допустимом статусе.</li><li>В описании change задать reviewer конкретные вопросы о контракте и побочных переходах. «Approve» без предмета не подтверждает выбранную семантику.</li><li>До merge назначить проверку после merge: контур, сигнал, срок и исполнитель. Если проверка недоступна, открыть связанную задачу и сохранить это ограничение.</li><li>Закрыть дефект только после записи результата. При отрицательном результате создать новую задачу с тем же входом и причиной, а не переписывать историю.</li></ol>\n<h2>Ограничения</h2>\n<p><code>CODEOWNERS</code> зависит от хостинга. Синтаксис и поведение GitHub нельзя без проверки переносить в GitLab, Bitbucket или внутреннюю платформу. Если автоматического маршрута нет, карта ролей в задаче всё равно работает, но людей нужно запросить вручную.</p>\n<p>История Git может потерять смысл после массового форматирования, squash, переноса или копирования кода. Опции <code>-M</code> и <code>-C</code> помогают искать перемещённые строки, но не восстанавливают решение, которого никогда не записали. Контракт и тест остаются отдельными источниками фактов.</p>\n<p>Review не является эксплуатационной проверкой. Approval сигнализирует, что reviewer считает изменения готовыми к merge; comment и request changes означают другие решения review. Ни один из этих исходов не показывает, что gateway прислал нужное значение в рабочем контуре и что экран обработал его без побочного эффекта. Это нужно проверять отдельно.</p>\n<p>Учебный тест на <code>unknown</code> не доказывает корректность платежного процесса, безопасность логов или полноту всех статусов. Нельзя объявлять production-исправление по результату локального примера. Для реального выпуска понадобятся согласованный контракт, интеграционная проверка, доступный сигнал и план отката.</p>\n<h2>Критерий готовности</h2>\n<p>Разбор готов, если команда может показать четыре вещи: источник исторического факта, владельца решения с формулировкой контракта, маршрут reviewer для изменённых путей и отдельную запись о проверке после merge. Тест должен падать, если неизвестный статус открывает success. При несовпадении пути или отсутствии владельца pipeline review должен остановить продвижение либо явно показать ручной шаг.</p>\n<p>Этот критерий не обещает production-результат. Он проверяет происхождение решения и отрицательный путь: команда знает, кто отвечает на каждый вопрос, а неизвестное значение не проходит в успешное действие молча.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: About code owners</a> — расположение файла, base branch, совпадение путей, доступ владельцев и приоритет последнего правила.</li><li><a href=\"https://git-scm.com/docs/git-blame/2.23.0.html\" target=\"_blank\" rel=\"noopener noreferrer\">Git documentation: git-blame</a> — revision и автор, которые последними изменили каждую строку, ограничение диапазона через <code>-L</code> и границы метода.</li><li><a href=\"https://git-scm.com/docs/git-log/2.23.0.html\" target=\"_blank\" rel=\"noopener noreferrer\">Git documentation: git-log</a> — поиск истории файла, следование за переименованием через <code>--follow</code> и поиск изменения строки через <code>-S</code>.</li><li><a href=\"https://docs.github.com/en/pull-requests/concepts/giving-reviews\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: Giving reviews</a> — комментарий, approval и запрос изменений как разные результаты review.</li></ul>"
}