8 lines
20 KiB
JSON
8 lines
20 KiB
JSON
{
|
||
"index": 291,
|
||
"slug": "editorial-2019-12-practice-code-ownership",
|
||
"title": "Ответственность за код: как назначать владельцев на стыке модулей",
|
||
"excerpt": "Последний коммит показывает историю строки, но не назначает владельца решения. Разбираем четыре роли, проверяем маршрут CODEOWNERS и не пропускаем неизвестный статус в success.",
|
||
"contentHtml": "<p>Ошибка появляется на стыке checkout и платёжного gateway. В журнале виден неизвестный статус, а экран показывает успешную оплату. Один разработчик ищет строку в parser, другой открывает последний коммит, третий ждёт ответа команды интеграции. Исправление задерживается, а следующий дефект повторяет ту же ошибку в соседнем файле. Цена — неверное состояние для пользователя, ручное расследование и потерянное время нескольких команд.</p>\n<p>В такой ситуации полезно сначала назначить не человека, а вопросы. Какой результат разрешён контрактом? Где проходит путь изменения? Кто проверит риск в pull request? Кто посмотрит на результат после merge? Если на все вопросы ответить «последний автор строки», команда подменяет исторический факт решением о текущей ответственности.</p>\n<h2>Тезис: одно имя не описывает ответственность</h2>\n<p>Последний автор строки не обязан быть владельцем решения. Он мог перенести код, исправить форматирование или добавить временный обход. История Git отвечает на вопрос «кто менял строку». Она не отвечает на вопрос «что должен означать новый статус». Владелец пути знает реализацию и соседние переходы. Владелец решения подтверждает смысл контракта. Reviewer проверяет заданный риск. После merge отдельный исполнитель проверяет наблюдаемый результат.</p>\n<p>Один человек может взять все роли. Но в задаче их стоит записать отдельно. Это уменьшает число скрытых предположений: если решение не принято, его нельзя выдать за результат ревью. Если путь не найден, согласование контракта не означает, что код изменён. Если follow-up не назначен, merge не доказывает, что пользовательский симптом исчез.</p>\n<h2>Сначала зафиксируйте наблюдаемый симптом</h2>\n<p>Начните с входа, неверного результата и места наблюдения. Фраза «сломался checkout» слишком широка. Формулировка «gateway вернул <code>unknown</code>, а обработчик перевёл его в <code>success</code>» задаёт проверяемую границу. В ней есть значение, преобразование и пользовательский эффект.</p>\n<p>Затем отделите факт от гипотезы. Фактом будет ответ gateway, строка преобразования, запись в журнале или результат теста. Гипотеза — что именно должен делать продукт с неизвестным значением. Её подтверждает владелец решения по контракту, а не автор строки и не число совпавших поисковых результатов.</p>\n<h2>Четыре роли для одного изменения</h2>\n<p><strong>Владелец решения</strong> подтверждает допустимые состояния и поведение на границе контракта. Для платежа он отвечает, означает ли <code>unknown</code> ожидание, ошибку, ручную проверку или отдельный безопасный экран.</p>\n<p><strong>Владелец пути кода</strong> находит parser, обработчик, экран, retry и тесты, которые действительно участвуют в переходе. Его задача — не решить бизнес-смысл, а показать, где изменение повлияет на поведение и какие соседние ветки нужно проверить.</p>\n<p><strong>Reviewer</strong> получает конкретный риск. Вопрос «всё ли нормально?» плохо проверяем. Вопросы «может ли неизвестное значение попасть в success?» и «сохраняется ли диагностический идентификатор?» задают наблюдаемый результат ревью.</p>\n<p><strong>Владелец последующей проверки</strong> смотрит на результат после merge. Это может быть тестовый сценарий, журнал, метрика или ручная проверка экрана. Такая роль не подтверждает заранее, что изменение сработает: она фиксирует, что именно проверено и с каким исходом.</p>\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>Контракт, решение владельца, отрицательный тест</td><td>Назначить владельца решения</td></tr><tr><td>Review не пришёл</td><td>Какой путь и какое правило совпали?</td><td>Путь файла, версия CODEOWNERS из base branch, права</td><td>Исправить маршрут или запросить reviewer вручную</td></tr><tr><td>PR одобрен, но риск не назван</td><td>Что именно проверил reviewer?</td><td>Ответ на контрактный вопрос и проверка отрицательной ветки</td><td>Уточнить review до merge</td></tr><tr><td>После merge нет результата</td><td>Кто и где проверит симптом?</td><td>Runbook, тест, журнал или отдельная задача с исходом</td><td>Назначить follow-up и срок проверки</td></tr></tbody></table></div>\n<h2>Что история Git показывает, а чего не показывает</h2>\n<p><code>git blame</code> показывает revision и автора, который последним изменил строку. Используйте его, чтобы найти контекст изменения, а не чтобы автоматически назначить владельца. Затем <code>git log --follow -- path/to/file</code> может продолжить историю одного файла за переименованием. Опция относится к одному файлу; она не просматривает автоматически всю папку и не заменяет чтение контракта.</p>\n<pre><code>git blame -L 42,58 -- src/checkout/gateway-status.js\ngit log --follow -- src/checkout/gateway-status.js\ngit show <revision> -- src/checkout/gateway-status.js</code></pre>\n<p>Эта последовательность даёт три разных факта: кто менял диапазон, какие коммиты относятся к пути и что именно изменилось в найденной revision. После неё всё равно нужно проверить потребителей статуса и тесты. История помогает восстановить контекст, но не служит каталогом актуальной экспертизы.</p>\n<h2>Как работает маршрут CODEOWNERS</h2>\n<p><code>CODEOWNERS</code> — механизм GitHub для назначения пользователей или команд, ответственных за пути. GitHub может автоматически запросить review у владельцев, если pull request меняет принадлежащий им файл. Для маршрутизации используется файл из base branch pull request, поэтому новая запись только в feature branch ещё не доказывает, что запрос сработает.</p>\n<p>Порядок правил важен: для одного пути приоритет получает последнее совпавшее правило. Поэтому общее правило обычно ставят выше частного, а частное — ниже; проверять нужно именно фактическое совпадение, регистр пути и доступ владельца. Пользователь или команда должны иметь права записи в репозитории. Само совпадение пути не подтверждает бизнес-смысл решения и не гарантирует, что reviewer ответит на нужный вопрос.</p>\n<pre><code># .github/CODEOWNERS\n* @example/platform-review\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>Имена в примере вымышлены. Для файла <code>/web/checkout/gateway/status.js</code> последним совпадёт правило <code>/web/checkout/gateway/</code>, поэтому оно перекроет более раннее общее правило. Для документа контракта сработает отдельная строка и назначит двух владельцев на одной строке. В рабочем репозитории замените псевдонимы существующими пользователями или командами и проверьте их права.</p>\n<figure><img src=\"/assets/editorial/2019/code-ownership-three-surfaces-2019.svg\" alt=\"Схема ведёт от симптома unknown status стал success к четырём ответам: владелец решения, путь кода, reviewer и проверка после merge; Git history показана как источник контекста.\" loading=\"lazy\" /><figcaption>Один дефект требует четырёх ответов. История Git даёт контекст строки, а CODEOWNERS помогает направить review по пути.</figcaption></figure>\n<h2>Воспроизводимый пример: unknown не должен стать success</h2>\n<p>Ниже — учебный сценарий, а не описание production-инцидента. Правило <code>unknown</code> в нём проектное: неизвестный ответ не считается ни успешной оплатой, ни отказом. В реальном продукте это решение должен подтвердить владелец контракта.</p>\n<pre><code>function mapGatewayStatus(status) {\n if (status === 'paid') return { state: 'success' };\n if (status === 'pending') return { state: 'pending' };\n return { state: 'unknown', diagnostic: 'gateway-status-unmapped' };\n}\n\nconst cases = ['paid', 'pending', 'unknown'];\nconst results = cases.map((status) => ({\n status,\n result: mapGatewayStatus(status),\n}));\n\nconsole.table(results);\n// unknown: state = 'unknown', success недопустим</code></pre>\n<p>Проверка воспроизводима: запустите фрагмент в Node.js или браузерной консоли и убедитесь, что три входа дают три ожидаемых результата. Затем добавьте тест, который явно отвергает <code>unknown → success</code>. Это не доказывает правильность всего checkout, но фиксирует одну границу, о которой reviewer может задать точный вопрос.</p>\n<p>Владелец решения подтверждает смысл <code>unknown</code>. Владелец пути проверяет обработчик, retry, экран и потребителей поля <code>diagnostic</code>. Reviewer проверяет, что неизвестное значение не попадает в success и что диагностический след не теряется. Владелец последующей проверки смотрит на выбранный сигнал после merge. Если решение по контракту не найдено, кодовый change нельзя считать завершённым только потому, что пример выглядит разумно.</p>\n<h2>Порядок действий</h2>\n<ol><li>Запишите один симптом: вход, неверный результат, путь, место наблюдения и цену ошибки.</li><li>Разделите факт и гипотезу. Подтвердите ответ gateway и текущий переход по журналу, коду или тесту.</li><li>Проследите путь через <code>git blame</code>, <code>git log --follow</code> и просмотр diff. Помните, что история одного файла не описывает всех потребителей.</li><li>Назначьте владельца решения и владельца пути. Не меняйте смысл статуса по имени последнего автора.</li><li>Проверьте CODEOWNERS в base branch: расположение файла, регистр пути, последнее совпадение и права пользователей или команд.</li><li>Откройте небольшой change с тестом отрицательного случая. В описании задайте каждому reviewer один конкретный вопрос о риске.</li><li>Зафиксируйте результат review. Comment, Approve и Request changes — разные решения; наличие имени в списке владельцев не равно ответу на контракт.</li><li>До merge назначьте последующую проверку. После выпуска запишите сигнал и его исход либо создайте отдельную задачу с исполнителем.</li></ol>\n<h2>Ограничения модели</h2>\n<p><code>CODEOWNERS</code> знает путь и правила конкретной платформы. Он не служит каталогом экспертизы, не подтверждает бизнес-смысл и не проверяет production. Требование обязательного одобрения владельца появляется только при соответствующей настройке защиты ветки или ruleset. Если маршрут не настроен, ручной запрос reviewer всё равно не заменяет решение владельца.</p>\n<p>GitHub — только один вариант реализации. В другой системе может не быть CODEOWNERS или автоматического запроса. Сохраните ту же модель в issue или шаблоне pull request: владелец решения, владелец пути, reviewer и последующая проверка. Меняется инструмент, но не вопросы, на которые должен ответить change.</p>\n<p>Не превращайте карту владения в процесс ради процесса. Для небольшого модуля достаточно четырёх полей, одного отрицательного теста и двух конкретных вопросов. Обновляйте маршрут, когда каталог переезжает, путь меняет регистр или команда меняет границу. Устаревший список создаёт ложное чувство контроля.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Изменение готово, если независимый разработчик может открыть задачу и найти четыре ответа: кто подтвердил смысл решения, где лежит изменённый путь, кто проверил риск и кто проверит результат после merge. Тест не допускает превращения <code>unknown</code> в <code>success</code>. Review содержит ответ на контрактный вопрос. Маршрут ownership проверен в base branch. После merge есть записанный сигнал или отдельная задача с назначенным исполнителем.</p>\n<p>Если хотя бы одного ответа нет, закрывайте кодовый change только как промежуточный результат. Это честнее, чем назвать исправление завершённым по одному факту merge.</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> — расположение CODEOWNERS, base branch, порядок совпадений, автоматический запрос review и требования к доступу.</li><li><a href=\"https://docs.github.com/en/pull-requests/reference/pull-request-reviews\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: Pull request reviews</a> — различия Comment, Approve и Request changes, а также порядок запроса review.</li><li><a href=\"https://git-scm.com/docs/git-blame\" target=\"_blank\" rel=\"noopener noreferrer\">Git documentation: git-blame</a> — историческая аннотация строк, revision и авторов изменений.</li><li><a href=\"https://git-scm.com/docs/git-log\" target=\"_blank\" rel=\"noopener noreferrer\">Git documentation: git-log</a> — область действия <code>--follow</code> и просмотр истории одного файла за переименованием.</li></ul>"
|
||
}
|