diff --git a/editorial/agent-rewrites/253.json b/editorial/agent-rewrites/253.json index 07afe3e..a2869cf 100644 --- a/editorial/agent-rewrites/253.json +++ b/editorial/agent-rewrites/253.json @@ -3,5 +3,5 @@ "slug": "editorial-2020-12-field-incident-review", "title": "Разбор инцидента: как не принять обход за исправление", "excerpt": "Запрос возвращает blocked, потому что обязательное поле теряется на границе mapper. Разбираем, как отделить факт от гипотезы, проверить обратимый обход и оставить защиту от повторения.", - "contentHtml": "
Запрос preview-order возвращает blocked, хотя вход выглядит допустимым. После преобразования объекта пропадает обязательное поле currency. Команда возвращает прежний mapper, получает allowed и закрывает задачу. Цена ошибки проявится позже: новый mapper снова попадёт в путь, поле снова исчезнет, а старый обход уже будут считать исправлением.
Восстановление сервиса и устранение причины — разные события. Временное действие должно иметь условие запуска, ожидаемый результат и путь отмены. Отдельно нужно записать тест или контракт, который не даст ошибке вернуться. Иначе отчёт сохраняет уверенность, но не сохраняет способ проверки.
\nРассмотрим только переход от mapper к границе pricing-adapter. Это учебная fixture в памяти. Она не обращается к сети, базе, очереди или реальному API. Вход содержит сумму и не содержит валюту. Новый mapper возвращает объект без currency. Затем preview-order отвечает blocked. Мы проверяем форму рассуждения, а не заявляем production-результат.
const input = { amount: 1000, itemId: 'demo-1' };\\n\\nconst mapped = newMapper(input);\\nif (!mapped.currency) {\\n return { status: 'blocked', reason: 'currency_missing' };\\n}\\n\\nconst fallback = oldMapper(input);\\nreturn { status: 'allowed', currency: fallback.currency };\nКод намеренно короткий. Он показывает место, где исчезает значение. Он не доказывает, что именно mapper стал единственной причиной отказа. Для такого вывода нужны наблюдения до и после границы, а также проверка альтернативных причин.
\nНаблюдение описывает то, что можно увидеть. «После mapper поле отсутствует» — наблюдение. «Новый mapper не переносит поле» — гипотеза. Она связывает два факта, но остаётся изменяемой. Если поле исчезло раньше, гипотеза не выдержит проверки, а факты останутся полезными.
\nДействие должно менять одну понятную переменную. В примере это возврат к прежнему mapper на ограниченной ветке. Проверка должна измерять именно это действие: fixture получает тот же вход, возвращает allowed и сохраняет currency. Такая проверка не доказывает исправность всех заказов. Она подтверждает только заданный сценарий.
Профилактика отвечает на другой вопрос: что поймает повторение? Здесь нужен контрактный тест, который отклоняет результат mapper без обязательного поля. Пока тест не написан и не прошёл, профилактика остаётся предложением. Статус planned честнее слова «готово», если изменение ещё не появилось в коде.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
preview-order вернул blocked | Нарушен контракт обязательного поля или сработала другая ветка отказа | Сохранить ответ и проверить вход на границе mapper | Не менять retry и timeout до локализации причины |
После mapper нет currency | Новый mapper мог отбросить поле | Сравнить вход, результат нового mapper и результат прежнего | Сформулировать узкую гипотезу H1 |
Прежний mapper даёт allowed | Обход возвращает известный контракт, но причина не устранена | Повторить fixture на том же входе и проверить сохранение поля | Оставить обход ограниченным и записать условие отмены |
| Проверка обхода не прошла | Гипотеза неполна или отказ вызван другой границей | Собрать новое наблюдение до следующего изменения | Отменить вывод и проверить альтернативную ветку |
| Ошибка возвращается после изменения mapper | Нет автоматической защиты контракта | Запустить тест на обязательное поле в точке передачи | Добавить контрактный тест и связать его с готовностью |
Увеличение timeout скрывает медленный ответ, но не возвращает пропавшее поле. Дополнительный retry повторяет тот же неверный объект и может умножить побочный эффект. Одновременная правка mapper, retry и timeout стирает причинную связь: если результат изменится, станет непонятно, какая правка помогла.
\nЭто не запрет на retry или timeout. Они уместны, когда наблюдение указывает на временную сетевую ошибку или ограничение времени ответа. Но тогда у решения должны быть собственный trigger и собственная проверка. Инструмент выбирают по наблюдаемому механизму, а не по привычке.
\nПредположим, возврат к прежнему mapper не дал allowed. Это не повод назвать прежний код неисправным. Новый факт говорит лишь о том, что выбранный обход не подтвердил гипотезу. Поле могло отсутствовать уже во входе. Отказ мог зависеть от другой обязательной величины. Вторая проверка должна отличить эти варианты.
Хороший разбор не прячет отрицательный результат. Он сохраняет его рядом с условием, при котором проверка должна была пройти. Так следующий инженер не повторит тот же обход вслепую. Формулировка «A1 не подтвердил H1 на входе E1» полезнее, чем «фикс не сработал»: первая фраза указывает границу нового исследования.
\nblocked на переходе к pricing-adapter.currency.Fixture не измеряет доступность, нагрузку, время восстановления, права доступа или поведение внешней зависимости. Она не заменяет журнал, мониторинг, резервирование и процедуру отката. Asset на схеме объясняет последовательность, но не является доказательством результата. Учебный код ограничен одним объектом и одним контрактом.
\nНельзя объявлять production-инцидент исправленным по одному успешному примеру. В реальной системе нужно подтвердить границу на фактическом запросе, проверить безопасный rollout и посмотреть на отрицательные случаи. Нельзя также превращать разбор в поиск виноватого: смена автора mapper не объясняет, почему контракт оказался без защиты.
\nРабота готова, когда выполнены четыре условия. Симптом воспроизводится на известном входе. Действие меняет только заявленную границу и проходит свою проверку. Отрицательный путь записан и приводит к новой гипотезе, а не к повторению того же обхода. Наконец, автоматическая проверка падает на результате mapper без currency и проходит на корректном результате.
Такой критерий не обещает, что система больше никогда не откажет. Он делает утверждение уже: конкретный контракт виден, действие проверено, а известный способ регрессии получает защиту.
\nПредставим разбор инцидента: запрос preview-order возвращает blocked, хотя вход выглядит допустимым. После преобразования объекта пропадает обязательное поле currency. Возврат к прежнему mapper даёт allowed, и команда закрывает задачу. Цена ошибки проявится позже: новый mapper снова попадёт в путь, поле снова исчезнет, а временный обход уже будут считать исправлением.
Восстановление сервиса и устранение причины — разные события. Временное действие должно иметь условие запуска, ожидаемый результат и путь отмены. Отдельно нужна проверка, которая не даст известному нарушению контракта вернуться. Ниже — учебный сценарий с воспроизводимой fixture, а не заявление о конкретном production-инциденте.
\nРассмотрим только переход от mapper к локальной функции previewOrder, которая имитирует границу pricing-adapter. Стенд не обращается к сети, базе, очереди или внешнему API. Вход содержит сумму, идентификатор товара и валюту. Новый mapper намеренно теряет currency, старый сохраняет все поля. Так мы проверяем одну гипотезу и не выдаём результат fixture за результат системы.
const input = { amount: 1000, itemId: 'demo-1', currency: 'RUB' };\n\nfunction newMapper(order) {\n return { amount: order.amount, itemId: order.itemId };\n}\n\nfunction oldMapper(order) {\n return { ...order };\n}\n\nfunction previewOrder(mapped) {\n if (!mapped.currency) {\n return { status: 'blocked', reason: 'currency_missing' };\n }\n return { status: 'allowed', currency: mapped.currency };\n}\n\nconst broken = previewOrder(newMapper(input));\nconst fallback = previewOrder(oldMapper(input));\n\nif (broken.status !== 'blocked' || fallback.status !== 'allowed') {\n throw new Error('fixture contract failed');\n}\n\nconsole.log({ broken, fallback });\nОжидаемый вывод — broken.status === 'blocked' с причиной currency_missing и fallback.status === 'allowed' с валютой RUB. Важна не удачная строка в консоли, а контраст двух результатов на одном входе. Если изменить вход или правило в previewOrder, вывод уже нельзя переносить на прежний сценарий.
Функции в примере определены прямо в fixture, поэтому её можно сохранить как небольшой файл Node.js и запустить без зависимостей. Это не интеграционный тест: он не проверяет сериализацию, сеть, базу или реальный код mapper. Эти границы нужно проверять отдельно.
\nНаблюдение описывает то, что можно увидеть: один и тот же вход дошёл до границы, после mapper поле отсутствует, а previewOrder вернул blocked. Гипотеза связывает наблюдения: новый mapper не переносит обязательное поле. Она правдоподобна, но не доказана, пока не сравнены вход и результат на этой границе.
До изменения сохраните четыре значения: входной объект, результат mapper, ответ проверки и идентификатор попытки. Если в настоящем сервисе есть лог или trace, запишите ссылку на него; если есть только локальная fixture, назовите её fixture. Не заменяйте отсутствующее наблюдение уверенной фразой «причина найдена».
\nПроверка должна менять одну переменную. В нашем случае новая версия mapper заменяется старой только на ограниченном пути. Если тот же вход даёт allowed и возвращает currency, гипотеза получает поддержку. Но это ещё не доказывает, что она объясняет все отказы: нужно проверить другие обязательные поля и реальные точки сериализации.
| Наблюдение | Гипотеза | Проверка | Владелец | Решение |
|---|---|---|---|---|
preview-order вернул blocked | Нарушен контракт обязательного поля или сработала другая ветка отказа | Сохранить ответ и проверить вход на границе mapper | Инженер, ведущий диагностику | Не менять retry и timeout до локализации причины |
После нового mapper нет currency | Mapper мог отбросить поле при сборке объекта | Сравнить вход, результат нового mapper и результат старого | Владелец адаптера | Сформулировать узкую гипотезу H1 |
Старый mapper даёт allowed | Обход возвращает нужный контракт, но причина не устранена | Повторить fixture на том же входе и проверить сохранение поля | Владелец релиза | Оставить обход ограниченным и записать условие отмены |
| Проверка обхода не прошла | Гипотеза неполна или отказ возник на другой границе | Собрать новое наблюдение до следующего изменения | Ведущий диагностики | Отменить вывод и проверить альтернативную ветку |
| Ошибка вернулась после изменения mapper | Для контракта нет автоматической защиты | Запустить тест на отсутствие обязательного поля | Владелец компонента | Добавить контрактную проверку и связать её с релизом |
Возврат к старому mapper может быть правильным способом уменьшить влияние ошибки. У него должны быть границы: какой маршрут переключается, какой сигнал включает обход, кто его подтверждает и по какому условию он снимается. Без этого временное решение превращается в новый постоянный путь, который никто не проверяет.
\nПосле восстановления отдельно зафиксируйте причинный вывод. В нашем примере он звучит узко: при входе с currency новый mapper возвращает объект без этого поля, а старый mapper поле сохраняет. Формулировка не обещает, что найден единственный дефект во всей системе. Она указывает конкретную границу, которую можно проверить.
Такое разделение совпадает с практикой управления инцидентами: во время сбоя нужны отдельные роли для операционной работы, коммуникации и координации, а после восстановления — живой документ с действиями и состоянием. Это снижает риск, что несколько инженеров одновременно изменят систему и сотрут причинную связь между изменением и результатом.
\nУвеличение timeout скрывает медленный ответ, но не возвращает пропавшее поле. Дополнительный retry повторяет тот же неверный объект и может умножить побочный эффект. Одновременная правка mapper, retry и timeout стирает причинную связь: если результат изменится, станет непонятно, какая правка помогла.
Это не запрет на retry или timeout. Они уместны, когда наблюдение указывает на временную сетевую ошибку или ограничение времени ответа. Но у такого решения должны быть собственный trigger, лимит повторов и собственная проверка. Инструмент выбирают по наблюдаемому механизму, а не по привычке.
\nПредположим, возврат к старому mapper не дал allowed. Это не повод объявить старый код неисправным. Новый факт говорит только о том, что выбранный обход не подтвердил H1. Поле могло отсутствовать уже во входе. Отказ мог зависеть от другой обязательной величины. Следующая проверка должна отличить эти варианты.
Проверим первый вариант: уберём currency из самого входа и запустим старый mapper. Поле останется отсутствующим, поэтому результат blocked больше не подтверждает гипотезу о новом mapper. Проверим второй вариант: оставим валюту, но уберём другой обязательный атрибут. Если отказ сохранится, причина находится не в потерянной валюте.
Хороший review не прячет отрицательный результат. Запись «A1 не подтвердил H1 на входе E1» полезнее, чем «фикс не сработал»: первая формулировка указывает границу нового исследования. Она также не обвиняет автора mapper и оставляет следующий шаг проверяемым.
\nblocked на переходе к pricing-adapter.currency и проходить на корректном объекте.Fixture не измеряет доступность, нагрузку, время восстановления, права доступа или поведение внешней зависимости. Она не заменяет журнал, мониторинг, резервирование и процедуру отката. Схема поясняет последовательность, но не является доказательством результата. Учебный код ограничен одним объектом, двумя mapper и одним обязательным полем.
\nНельзя объявлять production-инцидент исправленным по одному успешному примеру. В реальной системе нужно подтвердить границу на фактическом запросе, проверить безопасный rollout и посмотреть на отрицательные случаи. Нельзя также превращать разбор в поиск виноватого: смена автора mapper не объясняет, почему контракт оказался без защиты.
\nИсточники ниже подтверждают практики incident management и postmortem, но не подтверждают вымышленный запрос preview-order или состояние конкретного сервиса. Факты такого инцидента должны подтверждаться собственными логами, изменениями и конфигурацией мониторинга.
Работа готова, когда симптом воспроизводится на известном входе, действие меняет только заявленную границу и проходит свою проверку, а отрицательный путь приводит к новой гипотезе. Дополнительно автоматическая проверка должна падать на результате mapper без currency и проходить на корректном результате.
Этот критерий не обещает, что система больше никогда не откажет. Он делает более узкое утверждение: конкретный контракт виден, действие проверено, а известный способ регрессии получает автоматическую защиту. Такой результат можно связать с последующим изменением и проверить повторно.
\n