{ "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. Так история остаётся читаемой, а проверка не маскируется под редактирование документа.
\nADR отвечает на четыре вопроса: какая проблема наблюдалась, какое решение выбрали, какие альтернативы рассмотрели и какие последствия приняли. Поля Status, Context, Decision и Consequences образуют минимальный каркас. В расширенном шаблоне рядом появляются владельцы, факторы выбора и способ проверки.
\nЗапись не является приказом навсегда. Она говорит: «при этих условиях мы выбрали этот вариант». Условие может измениться из-за нового контракта, класса данных, требования к времени ответа, стоимости отказа или исчезновения владельца. Сам факт изменения кода ещё ничего не доказывает. Нужно показать, какое условие перестало выполняться.
\nПолезно разделять три объекта. ADR хранит rationale и границу решения. Тест или запрос к метрикам даёт evidence по конкретному вопросу. План изменения описывает выкладку, откат и наблюдение. Ни один объект не заменяет два других.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Код больше не совпадает с границей ADR | Изменился контракт или способ выполнения | Сопоставить diff с разделами Context и Decision | Создать Proposed successor, старый текст не менять |
| В записи есть assumption, но нет факта о её состоянии | ADR приняли за автоматическую проверку | Назвать источник, период, среду и результат, который опровергнет assumption | Открыть отдельную validation task |
| Наступила дата review, но новых данных нет | Календарный срок подменил сигнал | Проверить ссылки, владельца, ограничения и evidence gap | Reconfirm или запланировать проверку; не ставить Superseded |
| Старое решение кажется «неудобным» | Последствия стали дороже или изменился приоритет | Заново сравнить альтернативы по текущим критериям | Записать новый компромисс и цену миграции |
| Нужно отменить изменение | Successor ещё не принят или его проверка не закрыта | Проверить статус и ссылку на прежний Accepted ADR | Вернуться к известной записи, не стирать историю |
Начните с исходной границы. Запишите её одним предложением: «экспорт выполняется синхронно, если размер запроса не превышает установленный предел, а вызывающая сторона ждёт ответ». Затем назовите новый факт: например, вызывающая сторона должна видеть статус завершения, а время выполнения больше не ограничено коротким запросом.
\nНовый факт ещё не выбирает архитектуру. Сравните варианты: оставить синхронный путь, добавить очередь со статусом или запустить неограниченную фоновую работу. У каждого варианта есть владелец статуса, путь восстановления, поведение при повторе и цена поддержки. Если критерий «вызывающая сторона должна видеть статус» обязателен, первый вариант отпадает. Если нет владельца очереди и проверки восстановления, второй остаётся только предложением.
\nОтрицательный путь важен. Если проверка показала, что новый код не отменяет исходную границу, не создавайте successor ради даты review. Зафиксируйте результат проверки и подтвердите прежнее решение. Если evidence отсутствует, не превращайте отсутствие ошибки в доказательство пригодности. Статус должен остаться Proposed, пока ответственный не согласовал контекст и способ проверки.
\nНиже приведена условная модель. Она не читает репозиторий, не меняет ADR и не доказывает свойства реальной очереди. Функции только показывают порядок состояний: сначала формируется предложение, затем отдельная проверка возвращает его без автоматического принятия.
\nconst 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 в примере не означает, что такой контроль уже существует.
Ссылка из ADR должна вести к устойчивой границе: контракту, схеме, модулю или отдельному тесту. Ссылка не подтверждает соответствие сама по себе. Для каждого важного assumption задайте проверяемый вопрос. Например: «видит ли вызывающая сторона три состояния операции?» Ответ должен иметь источник, среду, период и критерий остановки.
\nМетрика отвечает только на тот вопрос, для которого её собрали. Низкая доля ошибок не подтверждает путь восстановления. Высокая пропускная способность не доказывает корректность прав доступа. Тест схемы не доказывает стоимость эксплуатации. Если один источник не покрывает риск, запишите это как ограничение, а не как скрытое условие готовности.
\nНе восстанавливайте прошлые мотивы по одному фрагменту кода. Составьте текущую запись: что система делает, какие факты доступны, какие неизвестны и кто отвечает за следующий вопрос. Доступный commit или тикет можно указать источником наблюдения. Нельзя выдавать его за доказательство первоначального rationale.
\nПосле срочного исправления особенно легко назвать временный обход Accepted архитектурой. Запишите срок действия, риск и условие удаления. Если команда не может назвать альтернативы и последствия, решение ещё не готово. Это честнее, чем создавать уверенную историю задним числом.
\nADR не запускает миграцию, не заменяет threat model, benchmark, тест-план, runbook или incident review. MADR и исходная форма Nygard предлагают структуру, но не устанавливают универсальные сроки review, роли согласования и веса критериев. Периодическая дата полезна только вместе с сигналом. Нельзя объявлять решение устаревшим из-за одной даты или одной метрики.
\nПроверяемый критерий готовности таков: для одного текущего ADR видны исходная assumption, подтверждающий источник, владелец проверки, отрицательный результат и действие при нём. Если нужен successor, он содержит ссылку назад, альтернативы, последствия, способ проверки и статус Proposed. Связь Superseded появляется только после явного принятия successor. После этого отдельный change plan проходит свои тесты и имеет путь отката.
\nЕсли хотя бы одного элемента нет, результатом должна быть открытая проверка или статус Proposed. Это не незавершённость документа. Это точное описание границы знания команды.
\n