From 4771f7584091b34c65f72282ec95606a08ab3389 Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Thu, 3 Sep 2026 21:34:19 +0300 Subject: [PATCH] editorial: refine incident review 253 --- editorial/agent-rewrites/253.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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

Восстановление сервиса и устранение причины — разные события. Временное действие должно иметь условие запуска, ожидаемый результат и путь отмены. Отдельно нужно записать тест или контракт, который не даст ошибке вернуться. Иначе отчёт сохраняет уверенность, но не сохраняет способ проверки.

\n

Граница примера

\n

Рассмотрим только переход от mapper к границе pricing-adapter. Это учебная fixture в памяти. Она не обращается к сети, базе, очереди или реальному API. Вход содержит сумму и не содержит валюту. Новый mapper возвращает объект без currency. Затем preview-order отвечает blocked. Мы проверяем форму рассуждения, а не заявляем production-результат.

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

Механизм: факт, гипотеза, действие, проверка

\n

Наблюдение описывает то, что можно увидеть. «После mapper поле отсутствует» — наблюдение. «Новый mapper не переносит поле» — гипотеза. Она связывает два факта, но остаётся изменяемой. Если поле исчезло раньше, гипотеза не выдержит проверки, а факты останутся полезными.

\n

Действие должно менять одну понятную переменную. В примере это возврат к прежнему mapper на ограниченной ветке. Проверка должна измерять именно это действие: fixture получает тот же вход, возвращает allowed и сохраняет currency. Такая проверка не доказывает исправность всех заказов. Она подтверждает только заданный сценарий.

\n

Профилактика отвечает на другой вопрос: что поймает повторение? Здесь нужен контрактный тест, который отклоняет результат mapper без обязательного поля. Пока тест не написан и не прошёл, профилактика остаётся предложением. Статус planned честнее слова «готово», если изменение ещё не появилось в коде.

\n
Как читать сигнал и выбирать следующий шаг
СимптомПричинаПроверкаДействие
preview-order вернул blockedНарушен контракт обязательного поля или сработала другая ветка отказаСохранить ответ и проверить вход на границе mapperНе менять retry и timeout до локализации причины
После mapper нет currencyНовый mapper мог отбросить полеСравнить вход, результат нового mapper и результат прежнегоСформулировать узкую гипотезу H1
Прежний mapper даёт allowedОбход возвращает известный контракт, но причина не устраненаПовторить fixture на том же входе и проверить сохранение поляОставить обход ограниченным и записать условие отмены
Проверка обхода не прошлаГипотеза неполна или отказ вызван другой границейСобрать новое наблюдение до следующего измененияОтменить вывод и проверить альтернативную ветку
Ошибка возвращается после изменения mapperНет автоматической защиты контрактаЗапустить тест на обязательное поле в точке передачиДобавить контрактный тест и связать его с готовностью
\n

Почему нельзя лечить все симптомы сразу

\n

Увеличение timeout скрывает медленный ответ, но не возвращает пропавшее поле. Дополнительный retry повторяет тот же неверный объект и может умножить побочный эффект. Одновременная правка mapper, retry и timeout стирает причинную связь: если результат изменится, станет непонятно, какая правка помогла.

\n

Это не запрет на retry или timeout. Они уместны, когда наблюдение указывает на временную сетевую ошибку или ограничение времени ответа. Но тогда у решения должны быть собственный trigger и собственная проверка. Инструмент выбирают по наблюдаемому механизму, а не по привычке.

\n
Цикл разбора инцидента: наблюдение, ограниченный обход, проверка и профилактика
Ограниченный обход снижает влияние сейчас. Контрактная проверка снижает риск повторения позже.
\n

Отрицательный путь

\n

Предположим, возврат к прежнему mapper не дал allowed. Это не повод назвать прежний код неисправным. Новый факт говорит лишь о том, что выбранный обход не подтвердил гипотезу. Поле могло отсутствовать уже во входе. Отказ мог зависеть от другой обязательной величины. Вторая проверка должна отличить эти варианты.

\n

Хороший разбор не прячет отрицательный результат. Он сохраняет его рядом с условием, при котором проверка должна была пройти. Так следующий инженер не повторит тот же обход вслепую. Формулировка «A1 не подтвердил H1 на входе E1» полезнее, чем «фикс не сработал»: первая фраза указывает границу нового исследования.

\n

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

\n
  1. Назвать один симптом и одну границу. В примере это blocked на переходе к pricing-adapter.
  2. Собрать факты до изменения: вход, результат mapper, ответ и сохранённые поля.
  3. Разделить факт и гипотезу. Не записывать возможную причину как доказанное наблюдение.
  4. Выбрать одно обратимое действие. Указать trigger, ожидаемый результат и условие отмены.
  5. Повторить тот же учебный сценарий или безопасный контролируемый запрос.
  6. Проверить результат только в пределах входа и среды. Не переносить его на неизвестные пути.
  7. Добавить профилактику с отдельным критерием: тест должен падать на результате mapper без currency.
  8. Закрыть работу только после проверки действия и профилактики. Если проверка отрицательна, начать новый цикл с наблюдения.
\n

Ограничения

\n

Fixture не измеряет доступность, нагрузку, время восстановления, права доступа или поведение внешней зависимости. Она не заменяет журнал, мониторинг, резервирование и процедуру отката. Asset на схеме объясняет последовательность, но не является доказательством результата. Учебный код ограничен одним объектом и одним контрактом.

\n

Нельзя объявлять production-инцидент исправленным по одному успешному примеру. В реальной системе нужно подтвердить границу на фактическом запросе, проверить безопасный rollout и посмотреть на отрицательные случаи. Нельзя также превращать разбор в поиск виноватого: смена автора mapper не объясняет, почему контракт оказался без защиты.

\n

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

\n

Работа готова, когда выполнены четыре условия. Симптом воспроизводится на известном входе. Действие меняет только заявленную границу и проходит свою проверку. Отрицательный путь записан и приводит к новой гипотезе, а не к повторению того же обхода. Наконец, автоматическая проверка падает на результате mapper без currency и проходит на корректном результате.

\n

Такой критерий не обещает, что система больше никогда не откажет. Он делает утверждение уже: конкретный контракт виден, действие проверено, а известный способ регрессии получает защиту.

\n

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

" + "contentHtml": "

Представим разбор инцидента: запрос preview-order возвращает blocked, хотя вход выглядит допустимым. После преобразования объекта пропадает обязательное поле currency. Возврат к прежнему mapper даёт allowed, и команда закрывает задачу. Цена ошибки проявится позже: новый mapper снова попадёт в путь, поле снова исчезнет, а временный обход уже будут считать исправлением.

\n

Восстановление сервиса и устранение причины — разные события. Временное действие должно иметь условие запуска, ожидаемый результат и путь отмены. Отдельно нужна проверка, которая не даст известному нарушению контракта вернуться. Ниже — учебный сценарий с воспроизводимой fixture, а не заявление о конкретном production-инциденте.

\n

Граница примера

\n

Рассмотрим только переход от mapper к локальной функции previewOrder, которая имитирует границу pricing-adapter. Стенд не обращается к сети, базе, очереди или внешнему API. Вход содержит сумму, идентификатор товара и валюту. Новый mapper намеренно теряет currency, старый сохраняет все поля. Так мы проверяем одну гипотезу и не выдаём результат fixture за результат системы.

\n
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, вывод уже нельзя переносить на прежний сценарий.

\n

Функции в примере определены прямо в fixture, поэтому её можно сохранить как небольшой файл Node.js и запустить без зависимостей. Это не интеграционный тест: он не проверяет сериализацию, сеть, базу или реальный код mapper. Эти границы нужно проверять отдельно.

\n

Сначала факт, потом гипотеза

\n

Наблюдение описывает то, что можно увидеть: один и тот же вход дошёл до границы, после mapper поле отсутствует, а previewOrder вернул blocked. Гипотеза связывает наблюдения: новый mapper не переносит обязательное поле. Она правдоподобна, но не доказана, пока не сравнены вход и результат на этой границе.

\n

До изменения сохраните четыре значения: входной объект, результат mapper, ответ проверки и идентификатор попытки. Если в настоящем сервисе есть лог или trace, запишите ссылку на него; если есть только локальная fixture, назовите её fixture. Не заменяйте отсутствующее наблюдение уверенной фразой «причина найдена».

\n

Проверка должна менять одну переменную. В нашем случае новая версия mapper заменяется старой только на ограниченном пути. Если тот же вход даёт allowed и возвращает currency, гипотеза получает поддержку. Но это ещё не доказывает, что она объясняет все отказы: нужно проверить другие обязательные поля и реальные точки сериализации.

\n
Связь наблюдения, проверки и решения
НаблюдениеГипотезаПроверкаВладелецРешение
preview-order вернул blockedНарушен контракт обязательного поля или сработала другая ветка отказаСохранить ответ и проверить вход на границе mapperИнженер, ведущий диагностикуНе менять retry и timeout до локализации причины
После нового mapper нет currencyMapper мог отбросить поле при сборке объектаСравнить вход, результат нового mapper и результат старогоВладелец адаптераСформулировать узкую гипотезу H1
Старый mapper даёт allowedОбход возвращает нужный контракт, но причина не устраненаПовторить fixture на том же входе и проверить сохранение поляВладелец релизаОставить обход ограниченным и записать условие отмены
Проверка обхода не прошлаГипотеза неполна или отказ возник на другой границеСобрать новое наблюдение до следующего измененияВедущий диагностикиОтменить вывод и проверить альтернативную ветку
Ошибка вернулась после изменения mapperДля контракта нет автоматической защитыЗапустить тест на отсутствие обязательного поляВладелец компонентаДобавить контрактную проверку и связать её с релизом
\n

Восстановление не равно исправлению

\n

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

\n

После восстановления отдельно зафиксируйте причинный вывод. В нашем примере он звучит узко: при входе с currency новый mapper возвращает объект без этого поля, а старый mapper поле сохраняет. Формулировка не обещает, что найден единственный дефект во всей системе. Она указывает конкретную границу, которую можно проверить.

\n

Такое разделение совпадает с практикой управления инцидентами: во время сбоя нужны отдельные роли для операционной работы, коммуникации и координации, а после восстановления — живой документ с действиями и состоянием. Это снижает риск, что несколько инженеров одновременно изменят систему и сотрут причинную связь между изменением и результатом.

\n

Почему нельзя лечить все симптомы сразу

\n

Увеличение timeout скрывает медленный ответ, но не возвращает пропавшее поле. Дополнительный retry повторяет тот же неверный объект и может умножить побочный эффект. Одновременная правка mapper, retry и timeout стирает причинную связь: если результат изменится, станет непонятно, какая правка помогла.

\n

Это не запрет на retry или timeout. Они уместны, когда наблюдение указывает на временную сетевую ошибку или ограничение времени ответа. Но у такого решения должны быть собственный trigger, лимит повторов и собственная проверка. Инструмент выбирают по наблюдаемому механизму, а не по привычке.

\n
Цикл разбора инцидента: факт, гипотеза, ограниченное действие, проверка и профилактика
Ограниченное действие снижает влияние сейчас. Контрактная проверка снижает риск повторения позже.
\n

Отрицательный путь обязателен

\n

Предположим, возврат к старому mapper не дал allowed. Это не повод объявить старый код неисправным. Новый факт говорит только о том, что выбранный обход не подтвердил H1. Поле могло отсутствовать уже во входе. Отказ мог зависеть от другой обязательной величины. Следующая проверка должна отличить эти варианты.

\n

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

\n

Хороший review не прячет отрицательный результат. Запись «A1 не подтвердил H1 на входе E1» полезнее, чем «фикс не сработал»: первая формулировка указывает границу нового исследования. Она также не обвиняет автора mapper и оставляет следующий шаг проверяемым.

\n

Порядок проверки

\n
  1. Назвать один симптом и одну границу. В примере это blocked на переходе к pricing-adapter.
  2. Собрать факты до изменения: вход, результат mapper, ответ и идентификатор попытки.
  3. Разделить факт и гипотезу. Возможную причину не записывать как доказанное наблюдение.
  4. Выбрать одно обратимое действие. Указать trigger, ожидаемый результат и условие отмены.
  5. Повторить тот же сценарий на том же входе или выполнить безопасный контролируемый запрос.
  6. Проверить результат только в пределах входа и среды. Не переносить fixture на неизвестные пути.
  7. Прогнать отрицательные варианты: поле отсутствует во входе, отказ вызван другим полем, обход не меняет ответ.
  8. Добавить контрактный тест: он должен падать на результате mapper без currency и проходить на корректном объекте.
  9. Закрыть работу только после проверки действия и профилактики. Если проверка отрицательна, начать новый цикл с наблюдения.
\n

Ограничения

\n

Fixture не измеряет доступность, нагрузку, время восстановления, права доступа или поведение внешней зависимости. Она не заменяет журнал, мониторинг, резервирование и процедуру отката. Схема поясняет последовательность, но не является доказательством результата. Учебный код ограничен одним объектом, двумя mapper и одним обязательным полем.

\n

Нельзя объявлять production-инцидент исправленным по одному успешному примеру. В реальной системе нужно подтвердить границу на фактическом запросе, проверить безопасный rollout и посмотреть на отрицательные случаи. Нельзя также превращать разбор в поиск виноватого: смена автора mapper не объясняет, почему контракт оказался без защиты.

\n

Источники ниже подтверждают практики incident management и postmortem, но не подтверждают вымышленный запрос preview-order или состояние конкретного сервиса. Факты такого инцидента должны подтверждаться собственными логами, изменениями и конфигурацией мониторинга.

\n

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

\n

Работа готова, когда симптом воспроизводится на известном входе, действие меняет только заявленную границу и проходит свою проверку, а отрицательный путь приводит к новой гипотезе. Дополнительно автоматическая проверка должна падать на результате mapper без currency и проходить на корректном результате.

\n

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

\n

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

" }