From 4bf7377b327701b9e5aa9ec89b56eb57c86ae4db Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Thu, 3 Sep 2026 22:54:03 +0300 Subject: [PATCH] =?UTF-8?q?editorial:=20=D1=83=D0=BB=D1=83=D1=87=D1=88?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D1=80=D0=B0=D0=B7=D0=B1=D0=BE=D1=80=20?= =?UTF-8?q?=D0=B2=D0=BB=D0=B0=D0=B4=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=B4=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- editorial/agent-rewrites/289.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editorial/agent-rewrites/289.json b/editorial/agent-rewrites/289.json index aeec38a..471e29f 100644 --- a/editorial/agent-rewrites/289.json +++ b/editorial/agent-rewrites/289.json @@ -3,5 +3,5 @@ "slug": "editorial-2019-12-field-code-ownership", "title": "Когда у ошибки четыре владельца: как разбирать код на стыке модулей", "excerpt": "Неизвестный статус проходит через gateway и checkout, а команда ищет автора последней строки. Разделяем историю кода, владельца решения, маршрут review и проверку после merge.", - "contentHtml": "

Checkout получает от gateway значение unknown, но показывает пользователю успешную оплату. В логах есть ответ интеграции, в коде есть ветка по умолчанию, а в задаче первым делом ищут автора строки через git blame. Ошибка превращается в спор: человек из checkout менял parser, команда gateway владеет API, а после merge никто не обязан проверить результат.

\n

Цена такого смешения — не только задержка. Неизвестное состояние может попасть в успешный сценарий, повторить неверное действие и породить второй дефект после исправления. Команда тратит время на поиск виноватого, но не фиксирует, кто принимает решение о контракте и кто проверяет его на рабочем пути.

\n

Тезис статьи простой: у одного дефекта могут быть разные владельцы. Исторический автор помогает восстановить контекст. Владелец пути кода знает, где менять поведение. Владелец решения определяет смысл статуса. Reviewer проверяет конкретный риск, а отдельный исполнитель подтверждает результат после merge. Эти роли могут совпасть, но их нельзя считать совпавшими без проверки.

\n

Что именно показывает каждый след

\n

git blame отвечает на исторический вопрос: какая revision последней изменила строку и кто её изменил. Это полезная точка входа. Автор мог сделать перенос, форматирование или механический рефакторинг. Из одной строки нельзя вывести, кто сегодня отвечает за смысл статуса.

\n

git log --follow добавляет контекст: какие изменения проходили через файл и где лежала прежняя версия. История помогает найти обсуждение, тест или документ контракта. Она не назначает текущего владельца. После переноса кода автор строки и эксперт по интеграции часто расходятся.

\n

В GitHub файл CODEOWNERS задаёт маршрут для запроса review по совпавшему пути. Для pull request используется версия файла из base branch. Последнее подходящее правило имеет приоритет. Это автоматизирует приглашение reviewer, но не доказывает, что человек согласовал семантику API или проверил поведение после merge.

\n
Четыре следа одного дефекта
След или рольНа какой вопрос отвечаетЧего не доказываетСледующее действие
git blameКто последним изменил строку?Кто владеет текущим правилом?Прочитать diff и связанные изменения
Путь кодаГде находится поведение и соседние переходы?Что новое значение означает для продукта?Найти тест, контракт и владельца модуля
CODEOWNERSКого платформа запросит на review?Что reviewer действительно подтвердил?Проверить base branch, правило и доступ команды
Review и follow-upЧто проверили до merge и кто проверит результат?Что ошибка исчезла сама по себе?Записать решение и наблюдаемый критерий
\n

Таблица задаёт границы ответственности. Она не требует создавать четыре должности. Один разработчик может закрыть все роли в маленьком модуле. На стыке gateway и checkout роли расходятся чаще, поэтому их нужно назвать в задаче или описании pull request.

\n
\"Учебная
Учебная схема: исторический автор, владелец решения, reviewer и исполнитель проверки отвечают за разные вопросы.
\n

Учебный сценарий на стыке gateway и checkout

\n

Ниже приведён искусственный пример. Он показывает маршрут расследования и не описывает реальный инцидент, пользователей, команду или production-результат. Предположим, gateway возвращает JSON с полем status, а checkout переводит его в состояние экрана.

\n
const viewByStatus = {\n  paid: 'success',\n  pending: 'waiting',\n  failed: 'error',\n};\n\nexport function getView(status) {\n  return viewByStatus[status] || 'success';\n}
\n

Ошибка находится в значении по умолчанию. Если gateway добавит review, checkout покажет успех. Даже если автор этой строки давно ушёл из команды, вопрос остаётся техническим: допустимо ли считать неизвестный статус успешным? Владелец контракта должен ответить «нет» или обосновать другое правило.

\n

Безопаснее сделать неизвестное значение отдельным состоянием и сохранить его для диагностики:

\n
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}
\n

В этом учебном коде unknown не разрешает подтверждение оплаты. Названия состояний, способ отображения и политика повторного запроса зависят от конкретного продукта. Нельзя переносить их в рабочую систему без проверки контракта. Но граница решения ясна: только явное значение paid открывает успешное действие.

\n

Проверка должна покрыть и отрицательный путь:

\n
test('does not treat an unknown status as paid', () => {\n  expect(getView('review')).toBe('unknown');\n  expect(canConfirmPayment('review')).toBe(false);\n});
\n

Этот тест не доказывает, что gateway всегда присылает корректные данные. Он доказывает только выбранный локальный контракт: неизвестное значение не становится успешным. Отдельно нужно решить, где логировать исходный статус, как показать пользователю безопасное состояние и кто проверит экран после merge.

\n

Разбор симптома

\n
Симптом → причина → проверка → действие
СимптомПричинаПроверкаДействие
В задаче назначен автор строкиИсторию приняли за владение решениемСравнить git blame с контрактом и текущей командой модуляНазвать отдельно owner решения и owner пути
Reviewer не получил запросПуть не совпал или CODEOWNERS изменён только в feature branchПроверить файл в base branch, порядок правил и доступ командыИсправить маршрут или запросить review вручную
Review зелёный, но статус всё ещё неверенReviewer проверил форму diff, а не смысл контрактаНайти в review конкретный вопрос о unknown и тест отрицательного случаяДобавить проверяемое решение и повторить review
После merge нет ответа о результатеFollow-up не назначили до измененияНайти сигнал, срок и исполнителя проверкиСоздать отдельное действие; не закрывать технический след одним merge
Неизвестный статус открывает оплатуВетка по умолчанию разрешает successПодменить вход в тесте на новое значениеСделать allowlist для успешного состояния и заблокировать отрицательный путь
\n

Проверка должна отделять факт от гипотезы. Если CODEOWNERS не отправил запрос, сначала проверяют расположение файла и правила. Если тест не проходит, сначала фиксируют вход и ожидаемый результат. Имя последнего автора не закрывает ни один из этих вопросов.

\n

Как оформить минимальный контракт

\n

Для учебного примера достаточно записать четыре поля в задаче:

\n
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
\n

Имена здесь условные. В настоящем проекте нужно указать реальные команды, путь, тест и канал проверки. Поле decision owner означает не «кто будет чинить», а «кто может подтвердить смысл допустимых состояний». Поле follow-up owner означает не гарантию успеха, а конкретное действие после merge.

\n

Для такого дерева путей учебная конфигурация может выглядеть так:

\n
*                          @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
\n

Псевдонимы вымышлены. Смысл примера — показать порядок: узкий путь gateway расположен после общего пути checkout. На GitHub несколько owners должны находиться в одной строке. Правило не заменяет проверку доступа и не переносит владельца решения из документа в платформу автоматически.

\n

Порядок действий

\n
  1. Записать точный симптом: вход gateway, значение статуса, экран и неверное действие. Не начинать с имени сотрудника.
  2. Ограничить расследование нужным путем и строками. Выполнить git blame -L, затем прочитать diff и историю связанных файлов через git log.
  3. Найти контракт, тест или документ, где определены допустимые статусы. Если правило не сформулировано, сначала назначить владельца решения.
  4. Назвать отдельно владельца решения, владельца пути, reviewer и follow-up. Разрешить одному человеку закрыть несколько ролей, но записать это явно.
  5. Проверить CODEOWNERS в base branch. Убедиться, что совпадает нужный путь, последнее правило имеет ожидаемый приоритет, а команда имеет требуемый доступ.
  6. Добавить тест на прошлый сбой и отрицательный тест для неизвестного значения. Успешный путь должен проходить только при явном допустимом статусе.
  7. В описании change задать reviewer конкретные вопросы о контракте и побочных переходах. «Approve» без предмета не подтверждает выбранную семантику.
  8. До merge назначить проверку после merge: контур, сигнал, срок и исполнитель. Если проверка недоступна, открыть связанную задачу и сохранить это ограничение.
  9. Закрыть дефект только после записи результата. При отрицательном результате создать новую задачу с тем же входом и причиной, а не переписывать историю.
\n

Ограничения

\n

CODEOWNERS зависит от хостинга. Синтаксис и поведение GitHub нельзя без проверки переносить в GitLab, Bitbucket или внутреннюю платформу. Если автоматического маршрута нет, карта ролей в задаче всё равно работает, но людей нужно запросить вручную.

\n

История Git может потерять смысл после массового форматирования, squash, переноса или копирования кода. Опции -M и -C помогают искать перемещённые строки, но не восстанавливают решение, которого никогда не записали. Контракт и тест остаются отдельными источниками фактов.

\n

Review не является эксплуатационной проверкой. Approval подтверждает состояние предложенного diff по правилам платформы и команды. Он не показывает, что gateway прислал нужное значение в рабочем контуре и что экран обработал его без побочного эффекта. Это нужно проверять отдельно.

\n

Учебный тест на unknown не доказывает корректность платежного процесса, безопасность логов или полноту всех статусов. Нельзя объявлять production-исправление по результату локального примера. Для реального выпуска понадобятся согласованный контракт, интеграционная проверка, доступный сигнал и план отката.

\n

Критерий готовности

\n

Разбор готов, если команда может показать четыре вещи: источник исторического факта, владельца решения с формулировкой контракта, маршрут reviewer для изменённых путей и отдельную запись о проверке после merge. Тест должен падать, если неизвестный статус открывает success. При несовпадении пути или отсутствии владельца pipeline review должен остановить продвижение либо явно показать ручной шаг.

\n

Этот критерий не обещает production-результат. Он проверяет происхождение решения и отрицательный путь: команда знает, кто отвечает на каждый вопрос, а неизвестное значение не проходит в успешное действие молча.

\n

Проверяемые источники

\n" + "contentHtml": "

Checkout получает от gateway значение unknown, но показывает пользователю успешную оплату. В логах есть ответ интеграции, в коде есть ветка по умолчанию, а в задаче первым делом ищут автора строки через git blame. Ошибка превращается в спор: человек из checkout менял parser, команда gateway владеет API, а после merge никто не обязан проверить результат.

\n

Цена такого смешения — не только задержка. Неизвестное состояние может попасть в успешный сценарий, повторить неверное действие и породить второй дефект после исправления. Команда тратит время на поиск виноватого, но не фиксирует, кто принимает решение о контракте и кто проверяет его на рабочем пути.

\n

Тезис статьи простой: у одного дефекта могут быть разные владельцы. Исторический автор помогает восстановить контекст. Владелец пути кода знает, где менять поведение. Владелец решения определяет смысл статуса. Reviewer проверяет конкретный риск, а отдельный исполнитель подтверждает результат после merge. Эти роли могут совпасть, но их нельзя считать совпавшими без проверки.

\n

Что именно показывает каждый след

\n

git blame отвечает на исторический вопрос: какая revision последней изменила строку и кто её изменил. Это полезная точка входа. Автор мог сделать перенос, форматирование или механический рефакторинг. Из одной строки нельзя вывести, кто сегодня отвечает за смысл статуса.

\n

git log --follow добавляет контекст: какие изменения проходили через файл и где лежала прежняя версия. История помогает найти обсуждение, тест или документ контракта. Она не назначает текущего владельца. После переноса кода автор строки и эксперт по интеграции часто расходятся.

\n
git blame -L 42,58 -- src/checkout/status.ts\ngit log --follow -- src/checkout/status.ts\ngit log -S"viewByStatus" -- src/checkout/status.ts
\n

Пути и номера строк в примере условны. Первая команда ограничивает blame нужным диапазоном, вторая следует за переименованием файла, а третья ищет изменения, в которых встречалась строка viewByStatus. История сужает поиск, но не назначает текущего владельца и не заменяет чтение контракта.

\n

В GitHub файл CODEOWNERS может автоматически запросить review по совпавшему пути. Для pull request используется версия файла из base branch. Если совпало несколько правил, последнее имеет приоритет. Это автоматизирует маршрут reviewer, но не доказывает, что человек согласовал семантику API или проверил поведение после merge.

\n
Четыре следа одного дефекта
След или рольНа какой вопрос отвечаетЧего не доказываетСледующее действие
git blameКто последним изменил строку?Кто владеет текущим правилом?Прочитать diff и связанные изменения
Путь кодаГде находится поведение и соседние переходы?Что новое значение означает для продукта?Найти тест, контракт и владельца модуля
CODEOWNERSКого платформа запросит на review?Что reviewer действительно подтвердил?Проверить base branch, правило и доступ команды
Review и follow-upЧто проверили до merge и кто проверит результат?Что ошибка исчезла сама по себе?Записать решение и наблюдаемый критерий
\n

Таблица задаёт границы ответственности. Она не требует создавать четыре должности. Один разработчик может закрыть все роли в маленьком модуле. На стыке gateway и checkout роли расходятся чаще, поэтому их нужно назвать в задаче или описании pull request.

\n
\"Учебная
Учебная схема: исторический автор, владелец решения, reviewer и исполнитель проверки отвечают за разные вопросы.
\n

Учебный сценарий на стыке gateway и checkout

\n

Ниже приведён искусственный пример. Он показывает маршрут расследования и не описывает реальный инцидент, пользователей, команду или production-результат. Предположим, gateway возвращает JSON с полем status, а checkout переводит его в состояние экрана.

\n
const viewByStatus = {\n  paid: 'success',\n  pending: 'waiting',\n  failed: 'error',\n};\n\nexport function getView(status) {\n  return viewByStatus[status] || 'success';\n}
\n

Ошибка находится в значении по умолчанию. Если gateway добавит review, checkout покажет успех. Даже если автор этой строки давно ушёл из команды, вопрос остаётся техническим: допустимо ли считать неизвестный статус успешным? Владелец контракта должен ответить «нет» или обосновать другое правило.

\n

Безопаснее сделать неизвестное значение отдельным состоянием и сохранить его для диагностики:

\n
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}
\n

В этом учебном коде unknown не разрешает подтверждение оплаты. Названия состояний, способ отображения и политика повторного запроса зависят от конкретного продукта. Нельзя переносить их в рабочую систему без проверки контракта. Но граница решения ясна: только явное значение paid открывает успешное действие.

\n

Проверка должна покрыть и отрицательный путь:

\n
test('does not treat an unknown status as paid', () => {\n  expect(getView('review')).toBe('unknown');\n  expect(canConfirmPayment('review')).toBe(false);\n});
\n

Этот тест не доказывает, что gateway всегда присылает корректные данные. Он доказывает только выбранный локальный контракт: неизвестное значение не становится успешным. Отдельно нужно решить, где логировать исходный статус, как показать пользователю безопасное состояние и кто проверит экран после merge.

\n

Разбор симптома

\n
Симптом → причина → проверка → действие
СимптомПричинаПроверкаДействие
В задаче назначен автор строкиИсторию приняли за владение решениемСравнить git blame с контрактом и текущей командой модуляНазвать отдельно owner решения и owner пути
Reviewer не получил запросПуть не совпал или CODEOWNERS изменён только в feature branchПроверить файл в base branch, порядок правил и доступ командыИсправить маршрут или запросить review вручную
Review зелёный, но статус всё ещё неверенReviewer проверил форму diff, а не смысл контрактаНайти в review конкретный вопрос о unknown и тест отрицательного случаяДобавить проверяемое решение и повторить review
После merge нет ответа о результатеFollow-up не назначили до измененияНайти сигнал, срок и исполнителя проверкиСоздать отдельное действие; не закрывать технический след одним merge
Неизвестный статус открывает оплатуВетка по умолчанию разрешает successПодменить вход в тесте на новое значениеСделать allowlist для успешного состояния и заблокировать отрицательный путь
\n

Проверка должна отделять факт от гипотезы. Если CODEOWNERS не отправил запрос, сначала проверяют расположение файла и правила. Если тест не проходит, сначала фиксируют вход и ожидаемый результат. Имя последнего автора не закрывает ни один из этих вопросов.

\n

Как оформить минимальный контракт

\n

Для учебного примера достаточно записать четыре поля в задаче:

\n
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
\n

Имена здесь условные. В настоящем проекте нужно указать реальные команды, путь, тест и канал проверки. Поле decision owner означает не «кто будет чинить», а «кто может подтвердить смысл допустимых состояний». Поле follow-up owner означает не гарантию успеха, а конкретное действие после merge.

\n

Для такого дерева путей учебная конфигурация может выглядеть так:

\n
*                          @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
\n

Псевдонимы вымышлены. Смысл примера — показать порядок: узкий путь gateway расположен после общего пути checkout. Если один pattern должен назначить несколько owners, на GitHub их записывают в одной строке. Правило не заменяет проверку доступа и не переносит владельца решения из документа в платформу автоматически.

\n

Порядок действий

\n
  1. Записать точный симптом: вход gateway, значение статуса, экран и неверное действие. Не начинать с имени сотрудника.
  2. Ограничить расследование нужным путем и строками. Выполнить git blame -L, затем прочитать diff и историю связанных файлов через git log.
  3. Найти контракт, тест или документ, где определены допустимые статусы. Если правило не сформулировано, сначала назначить владельца решения.
  4. Назвать отдельно владельца решения, владельца пути, reviewer и follow-up. Разрешить одному человеку закрыть несколько ролей, но записать это явно.
  5. Проверить CODEOWNERS в base branch. Убедиться, что совпадает нужный путь, последнее правило имеет ожидаемый приоритет, а команда имеет требуемый доступ.
  6. Добавить тест на прошлый сбой и отрицательный тест для неизвестного значения. Успешный путь должен проходить только при явном допустимом статусе.
  7. В описании change задать reviewer конкретные вопросы о контракте и побочных переходах. «Approve» без предмета не подтверждает выбранную семантику.
  8. До merge назначить проверку после merge: контур, сигнал, срок и исполнитель. Если проверка недоступна, открыть связанную задачу и сохранить это ограничение.
  9. Закрыть дефект только после записи результата. При отрицательном результате создать новую задачу с тем же входом и причиной, а не переписывать историю.
\n

Ограничения

\n

CODEOWNERS зависит от хостинга. Синтаксис и поведение GitHub нельзя без проверки переносить в GitLab, Bitbucket или внутреннюю платформу. Если автоматического маршрута нет, карта ролей в задаче всё равно работает, но людей нужно запросить вручную.

\n

История Git может потерять смысл после массового форматирования, squash, переноса или копирования кода. Опции -M и -C помогают искать перемещённые строки, но не восстанавливают решение, которого никогда не записали. Контракт и тест остаются отдельными источниками фактов.

\n

Review не является эксплуатационной проверкой. Approval сигнализирует, что reviewer считает изменения готовыми к merge; comment и request changes означают другие решения review. Ни один из этих исходов не показывает, что gateway прислал нужное значение в рабочем контуре и что экран обработал его без побочного эффекта. Это нужно проверять отдельно.

\n

Учебный тест на unknown не доказывает корректность платежного процесса, безопасность логов или полноту всех статусов. Нельзя объявлять production-исправление по результату локального примера. Для реального выпуска понадобятся согласованный контракт, интеграционная проверка, доступный сигнал и план отката.

\n

Критерий готовности

\n

Разбор готов, если команда может показать четыре вещи: источник исторического факта, владельца решения с формулировкой контракта, маршрут reviewer для изменённых путей и отдельную запись о проверке после merge. Тест должен падать, если неизвестный статус открывает success. При несовпадении пути или отсутствии владельца pipeline review должен остановить продвижение либо явно показать ручной шаг.

\n

Этот критерий не обещает production-результат. Он проверяет происхождение решения и отрицательный путь: команда знает, кто отвечает на каждый вопрос, а неизвестное значение не проходит в успешное действие молча.

\n

Проверяемые источники

\n" }