{ "index": 118, "slug": "editorial-2024-09-field-adr-decisions", "title": "ADR устарел: как проверить решение и не переписать его историю", "excerpt": "Старый ADR не становится неверным только потому, что изменился код. Разбираем признаки drift, проверку assumptions, successor-запись и безопасный переход от Accepted к Superseded.", "contentHtml": "

Через несколько месяцев после принятия ADR команда открывает его перед изменением сервиса. В документе описан синхронный экспорт для небольшого запроса. В коде уже появился фоновый worker и endpoint со статусом операции. Один разработчик предлагает просто исправить старый текст: заменить «синхронный экспорт» на «фоновый экспорт» и оставить прежнюю дату.

\n

Симптом виден сразу: ADR, код и текущий вопрос описывают разные границы системы. Цена ошибки выше, чем кажется. Команда теряет причину прежнего выбора, не видит принятые компромиссы и может повторно принять уже отвергнутый вариант. При аварии становится неясно, к какому решению возвращаться. При проверке доступа или миграции нельзя отличить старое обязательство от нового предложения.

\n

Тезис прост: ADR фиксирует принятое решение в конкретном контексте, но не проверяет систему сам. Когда меняется assumption или constraint, старую запись нужно сохранить, а новое решение оформить как successor. Только после его принятия старый ADR можно связать с ним статусом Superseded. Так история остаётся читаемой, а проверка не маскируется под редактирование документа.

\n

Что именно хранит ADR

\n

ADR отвечает на четыре вопроса: какая проблема наблюдалась, какое решение выбрали, какие альтернативы рассмотрели и какие последствия приняли. Поля Status, Context, Decision и Consequences образуют минимальный каркас. В расширенном шаблоне рядом появляются владельцы, факторы выбора и способ проверки.

\n

Запись не является приказом навсегда. Она говорит: «при этих условиях мы выбрали этот вариант». Условие может измениться из-за нового контракта, класса данных, требования к времени ответа, стоимости отказа или исчезновения владельца. Сам факт изменения кода ещё ничего не доказывает. Нужно показать, какое условие перестало выполняться.

\n

Полезно разделять три объекта. ADR хранит rationale и границу решения. Тест или запрос к метрикам даёт evidence по конкретному вопросу. План изменения описывает выкладку, откат и наблюдение. Ни один объект не заменяет два других.

\n

Симптом → причина → проверка → действие

\n
Признаки устаревания ADR и безопасное первое действие
СимптомПричинаПроверкаДействие
Код больше не совпадает с границей ADRИзменился контракт или способ выполненияСопоставить diff с разделами Context и DecisionСоздать Proposed successor, старый текст не менять
В записи есть assumption, но нет факта о её состоянииADR приняли за автоматическую проверкуНазвать источник, период, среду и результат, который опровергнет assumptionОткрыть отдельную validation task
Наступила дата review, но новых данных нетКалендарный срок подменил сигналПроверить ссылки, владельца, ограничения и evidence gapReconfirm или запланировать проверку; не ставить Superseded
Старое решение кажется «неудобным»Последствия стали дороже или изменился приоритетЗаново сравнить альтернативы по текущим критериямЗаписать новый компромисс и цену миграции
Нужно отменить изменениеSuccessor ещё не принят или его проверка не закрытаПроверить статус и ссылку на прежний Accepted ADRВернуться к известной записи, не стирать историю
\n

Механизм reassessment

\n

Начните с исходной границы. Запишите её одним предложением: «экспорт выполняется синхронно, если размер запроса не превышает установленный предел, а вызывающая сторона ждёт ответ». Затем назовите новый факт: например, вызывающая сторона должна видеть статус завершения, а время выполнения больше не ограничено коротким запросом.

\n

Новый факт ещё не выбирает архитектуру. Сравните варианты: оставить синхронный путь, добавить очередь со статусом или запустить неограниченную фоновую работу. У каждого варианта есть владелец статуса, путь восстановления, поведение при повторе и цена поддержки. Если критерий «вызывающая сторона должна видеть статус» обязателен, первый вариант отпадает. Если нет владельца очереди и проверки восстановления, второй остаётся только предложением.

\n

Отрицательный путь важен. Если проверка показала, что новый код не отменяет исходную границу, не создавайте successor ради даты review. Зафиксируйте результат проверки и подтвердите прежнее решение. Если evidence отсутствует, не превращайте отсутствие ошибки в доказательство пригодности. Статус должен остаться Proposed, пока ответственный не согласовал контекст и способ проверки.

\n

Условный пример с кодом

\n

Ниже приведена условная модель. Она не читает репозиторий, не меняет ADR и не доказывает свойства реальной очереди. Функции только показывают порядок состояний: сначала формируется предложение, затем отдельная проверка возвращает его без автоматического принятия.

\n
const oldRecord = {\n  id: 'adr-0012',\n  status: 'accepted',\n  boundary: 'small synchronous export'\n};\n\nconst successor = {\n  id: 'adr-0013',\n  status: 'proposed',\n  supersedes: oldRecord.id,\n  decision: 'queued export with visible status',\n  validation: 'caller observes pending, completed and failed states'\n};\n\nconst review = validateSuccessor(successor);\n\nif (review.accepted) {\n  oldRecord.status = 'superseded';\n} else {\n  oldRecord.status = 'accepted';\n  successor.status = 'proposed';\n}
\n

Главная защита находится в ветке else. Ошибка проверки не должна автоматически закрывать старую опору. В реальной системе функция проверки была бы тестом, ручным review, проверкой схемы или запросом с известным scope. Само поле accepted в примере не означает, что такой контроль уже существует.

\n
\"Цикл
Цикл reassessment: сигнал приводит к проверке контекста, ссылки на код и границы доказательства. Если assumption не выдерживает проверку, создаётся Proposed successor. Старый ADR получает Superseded только после принятия нового.
\n

Как связать запись с кодом и проверкой

\n

Ссылка из ADR должна вести к устойчивой границе: контракту, схеме, модулю или отдельному тесту. Ссылка не подтверждает соответствие сама по себе. Для каждого важного assumption задайте проверяемый вопрос. Например: «видит ли вызывающая сторона три состояния операции?» Ответ должен иметь источник, среду, период и критерий остановки.

\n

Метрика отвечает только на тот вопрос, для которого её собрали. Низкая доля ошибок не подтверждает путь восстановления. Высокая пропускная способность не доказывает корректность прав доступа. Тест схемы не доказывает стоимость эксплуатации. Если один источник не покрывает риск, запишите это как ограничение, а не как скрытое условие готовности.

\n

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

\n
  1. Откройте Accepted ADR и выпишите Context, Decision, Consequences, owner, ссылки и исходные assumptions.
  2. Назовите один наблюдаемый сигнал: изменился контракт, предел запроса, класс данных, владелец или требование к восстановлению.
  3. Сверьте сигнал с кодом и текущим контрактом. Отделите факт от предположения и сохраните источник проверки.
  4. Определите evidence boundary: кто проверяет, где, за какой период, каким артефактом и какой результат опровергнет assumption.
  5. Создайте successor в статусе Proposed. Добавьте ссылку на старый ADR, альтернативы, последствия, стоимость отката и критерий проверки.
  6. Проведите review. Если новое решение принято, свяжите записи и поставьте старой Superseded. Если отклонено, сохраните причину и оставьте старый ADR действующим.
  7. Проведите изменение отдельно: тесты, разрешения, выкладка, наблюдение, stop condition и rollback.
\n

Если старой записи нет

\n

Не восстанавливайте прошлые мотивы по одному фрагменту кода. Составьте текущую запись: что система делает, какие факты доступны, какие неизвестны и кто отвечает за следующий вопрос. Доступный commit или тикет можно указать источником наблюдения. Нельзя выдавать его за доказательство первоначального rationale.

\n

После срочного исправления особенно легко назвать временный обход Accepted архитектурой. Запишите срок действия, риск и условие удаления. Если команда не может назвать альтернативы и последствия, решение ещё не готово. Это честнее, чем создавать уверенную историю задним числом.

\n

Ограничения и критерий готовности

\n

ADR не запускает миграцию, не заменяет threat model, benchmark, тест-план, runbook или incident review. MADR и исходная форма Nygard предлагают структуру, но не устанавливают универсальные сроки review, роли согласования и веса критериев. Периодическая дата полезна только вместе с сигналом. Нельзя объявлять решение устаревшим из-за одной даты или одной метрики.

\n

Проверяемый критерий готовности таков: для одного текущего ADR видны исходная assumption, подтверждающий источник, владелец проверки, отрицательный результат и действие при нём. Если нужен successor, он содержит ссылку назад, альтернативы, последствия, способ проверки и статус Proposed. Связь Superseded появляется только после явного принятия successor. После этого отдельный change plan проходит свои тесты и имеет путь отката.

\n

Если хотя бы одного элемента нет, результатом должна быть открытая проверка или статус Proposed. Это не незавершённость документа. Это точное описание границы знания команды.

\n

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

" }