Files
progcode/editorial/agent-rewrites/253.json
T

8 lines
19 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 253,
"slug": "editorial-2020-12-field-incident-review",
"title": "Разбор инцидента: как не принять обход за исправление",
"excerpt": "Запрос возвращает blocked, потому что обязательное поле теряется на границе mapper. Разбираем, как отделить факт от гипотезы, проверить обратимый обход и оставить защиту от повторения.",
"contentHtml": "<p>Представим разбор инцидента: запрос <code>preview-order</code> возвращает <code>blocked</code>, хотя вход выглядит допустимым. После преобразования объекта пропадает обязательное поле <code>currency</code>. Возврат к прежнему mapper даёт <code>allowed</code>, и команда закрывает задачу. Цена ошибки проявится позже: новый mapper снова попадёт в путь, поле снова исчезнет, а временный обход уже будут считать исправлением.</p>\n<p>Восстановление сервиса и устранение причины — разные события. Временное действие должно иметь условие запуска, ожидаемый результат и путь отмены. Отдельно нужна проверка, которая не даст известному нарушению контракта вернуться. Ниже — учебный сценарий с воспроизводимой fixture, а не заявление о конкретном production-инциденте.</p>\n<h2>Граница примера</h2>\n<p>Рассмотрим только переход от mapper к локальной функции <code>previewOrder</code>, которая имитирует границу <code>pricing-adapter</code>. Стенд не обращается к сети, базе, очереди или внешнему API. Вход содержит сумму, идентификатор товара и валюту. Новый mapper намеренно теряет <code>currency</code>, старый сохраняет все поля. Так мы проверяем одну гипотезу и не выдаём результат fixture за результат системы.</p>\n<pre><code>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 });</code></pre>\n<p>Ожидаемый вывод — <code>broken.status === 'blocked'</code> с причиной <code>currency_missing</code> и <code>fallback.status === 'allowed'</code> с валютой <code>RUB</code>. Важна не удачная строка в консоли, а контраст двух результатов на одном входе. Если изменить вход или правило в <code>previewOrder</code>, вывод уже нельзя переносить на прежний сценарий.</p>\n<p>Функции в примере определены прямо в fixture, поэтому её можно сохранить как небольшой файл Node.js и запустить без зависимостей. Это не интеграционный тест: он не проверяет сериализацию, сеть, базу или реальный код mapper. Эти границы нужно проверять отдельно.</p>\n<h2>Сначала факт, потом гипотеза</h2>\n<p>Наблюдение описывает то, что можно увидеть: один и тот же вход дошёл до границы, после mapper поле отсутствует, а <code>previewOrder</code> вернул <code>blocked</code>. Гипотеза связывает наблюдения: новый mapper не переносит обязательное поле. Она правдоподобна, но не доказана, пока не сравнены вход и результат на этой границе.</p>\n<p>До изменения сохраните четыре значения: входной объект, результат mapper, ответ проверки и идентификатор попытки. Если в настоящем сервисе есть лог или trace, запишите ссылку на него; если есть только локальная fixture, назовите её fixture. Не заменяйте отсутствующее наблюдение уверенной фразой «причина найдена».</p>\n<p>Проверка должна менять одну переменную. В нашем случае новая версия mapper заменяется старой только на ограниченном пути. Если тот же вход даёт <code>allowed</code> и возвращает <code>currency</code>, гипотеза получает поддержку. Но это ещё не доказывает, что она объясняет все отказы: нужно проверить другие обязательные поля и реальные точки сериализации.</p>\n<div class='table-scroll'><table><caption>Связь наблюдения, проверки и решения</caption><thead><tr><th scope='col'>Наблюдение</th><th scope='col'>Гипотеза</th><th scope='col'>Проверка</th><th scope='col'>Владелец</th><th scope='col'>Решение</th></tr></thead><tbody><tr><td><code>preview-order</code> вернул <code>blocked</code></td><td>Нарушен контракт обязательного поля или сработала другая ветка отказа</td><td>Сохранить ответ и проверить вход на границе mapper</td><td>Инженер, ведущий диагностику</td><td>Не менять retry и timeout до локализации причины</td></tr><tr><td>После нового mapper нет <code>currency</code></td><td>Mapper мог отбросить поле при сборке объекта</td><td>Сравнить вход, результат нового mapper и результат старого</td><td>Владелец адаптера</td><td>Сформулировать узкую гипотезу H1</td></tr><tr><td>Старый mapper даёт <code>allowed</code></td><td>Обход возвращает нужный контракт, но причина не устранена</td><td>Повторить fixture на том же входе и проверить сохранение поля</td><td>Владелец релиза</td><td>Оставить обход ограниченным и записать условие отмены</td></tr><tr><td>Проверка обхода не прошла</td><td>Гипотеза неполна или отказ возник на другой границе</td><td>Собрать новое наблюдение до следующего изменения</td><td>Ведущий диагностики</td><td>Отменить вывод и проверить альтернативную ветку</td></tr><tr><td>Ошибка вернулась после изменения mapper</td><td>Для контракта нет автоматической защиты</td><td>Запустить тест на отсутствие обязательного поля</td><td>Владелец компонента</td><td>Добавить контрактную проверку и связать её с релизом</td></tr></tbody></table></div>\n<h2>Восстановление не равно исправлению</h2>\n<p>Возврат к старому mapper может быть правильным способом уменьшить влияние ошибки. У него должны быть границы: какой маршрут переключается, какой сигнал включает обход, кто его подтверждает и по какому условию он снимается. Без этого временное решение превращается в новый постоянный путь, который никто не проверяет.</p>\n<p>После восстановления отдельно зафиксируйте причинный вывод. В нашем примере он звучит узко: при входе с <code>currency</code> новый mapper возвращает объект без этого поля, а старый mapper поле сохраняет. Формулировка не обещает, что найден единственный дефект во всей системе. Она указывает конкретную границу, которую можно проверить.</p>\n<p>Такое разделение совпадает с практикой управления инцидентами: во время сбоя нужны отдельные роли для операционной работы, коммуникации и координации, а после восстановления — живой документ с действиями и состоянием. Это снижает риск, что несколько инженеров одновременно изменят систему и сотрут причинную связь между изменением и результатом.</p>\n<h2>Почему нельзя лечить все симптомы сразу</h2>\n<p>Увеличение <code>timeout</code> скрывает медленный ответ, но не возвращает пропавшее поле. Дополнительный <code>retry</code> повторяет тот же неверный объект и может умножить побочный эффект. Одновременная правка mapper, retry и timeout стирает причинную связь: если результат изменится, станет непонятно, какая правка помогла.</p>\n<p>Это не запрет на retry или timeout. Они уместны, когда наблюдение указывает на временную сетевую ошибку или ограничение времени ответа. Но у такого решения должны быть собственный trigger, лимит повторов и собственная проверка. Инструмент выбирают по наблюдаемому механизму, а не по привычке.</p>\n<figure><img src='/assets/editorial/2020/incident-learning-loop-2020.svg' alt='Цикл разбора инцидента: факт, гипотеза, ограниченное действие, проверка и профилактика' loading='lazy' /><figcaption>Ограниченное действие снижает влияние сейчас. Контрактная проверка снижает риск повторения позже.</figcaption></figure>\n<h2>Отрицательный путь обязателен</h2>\n<p>Предположим, возврат к старому mapper не дал <code>allowed</code>. Это не повод объявить старый код неисправным. Новый факт говорит только о том, что выбранный обход не подтвердил H1. Поле могло отсутствовать уже во входе. Отказ мог зависеть от другой обязательной величины. Следующая проверка должна отличить эти варианты.</p>\n<p>Проверим первый вариант: уберём <code>currency</code> из самого входа и запустим старый mapper. Поле останется отсутствующим, поэтому результат <code>blocked</code> больше не подтверждает гипотезу о новом mapper. Проверим второй вариант: оставим валюту, но уберём другой обязательный атрибут. Если отказ сохранится, причина находится не в потерянной валюте.</p>\n<p>Хороший review не прячет отрицательный результат. Запись «A1 не подтвердил H1 на входе E1» полезнее, чем «фикс не сработал»: первая формулировка указывает границу нового исследования. Она также не обвиняет автора mapper и оставляет следующий шаг проверяемым.</p>\n<h2>Порядок проверки</h2>\n<ol><li>Назвать один симптом и одну границу. В примере это <code>blocked</code> на переходе к <code>pricing-adapter</code>.</li><li>Собрать факты до изменения: вход, результат mapper, ответ и идентификатор попытки.</li><li>Разделить факт и гипотезу. Возможную причину не записывать как доказанное наблюдение.</li><li>Выбрать одно обратимое действие. Указать trigger, ожидаемый результат и условие отмены.</li><li>Повторить тот же сценарий на том же входе или выполнить безопасный контролируемый запрос.</li><li>Проверить результат только в пределах входа и среды. Не переносить fixture на неизвестные пути.</li><li>Прогнать отрицательные варианты: поле отсутствует во входе, отказ вызван другим полем, обход не меняет ответ.</li><li>Добавить контрактный тест: он должен падать на результате mapper без <code>currency</code> и проходить на корректном объекте.</li><li>Закрыть работу только после проверки действия и профилактики. Если проверка отрицательна, начать новый цикл с наблюдения.</li></ol>\n<h2>Ограничения</h2>\n<p>Fixture не измеряет доступность, нагрузку, время восстановления, права доступа или поведение внешней зависимости. Она не заменяет журнал, мониторинг, резервирование и процедуру отката. Схема поясняет последовательность, но не является доказательством результата. Учебный код ограничен одним объектом, двумя mapper и одним обязательным полем.</p>\n<p>Нельзя объявлять production-инцидент исправленным по одному успешному примеру. В реальной системе нужно подтвердить границу на фактическом запросе, проверить безопасный rollout и посмотреть на отрицательные случаи. Нельзя также превращать разбор в поиск виноватого: смена автора mapper не объясняет, почему контракт оказался без защиты.</p>\n<p>Источники ниже подтверждают практики incident management и postmortem, но не подтверждают вымышленный запрос <code>preview-order</code> или состояние конкретного сервиса. Факты такого инцидента должны подтверждаться собственными логами, изменениями и конфигурацией мониторинга.</p>\n<h2>Критерий готовности</h2>\n<p>Работа готова, когда симптом воспроизводится на известном входе, действие меняет только заявленную границу и проходит свою проверку, а отрицательный путь приводит к новой гипотезе. Дополнительно автоматическая проверка должна падать на результате mapper без <code>currency</code> и проходить на корректном результате.</p>\n<p>Этот критерий не обещает, что система больше никогда не откажет. Он делает более узкое утверждение: конкретный контракт виден, действие проверено, а известный способ регрессии получает автоматическую защиту. Такой результат можно связать с последующим изменением и проверить повторно.</p>\n<h2>Проверяемые источники</h2><ul><li><a href='https://sre.google/sre-book/managing-incidents/' target='_blank' rel='noopener noreferrer'>Google SRE Book: Managing Incidents</a> — официальная глава о разделении операционной работы, коммуникации, планирования, живом документе инцидента и передаче ответственности.</li><li><a href='https://sre.google/sre-book/postmortem-culture/' target='_blank' rel='noopener noreferrer'>Google SRE Book: Postmortem Culture</a> — официальное описание postmortem как записи влияния, действий, причин и follow-up; отдельно объясняет объективные триггеры и принцип без поиска виноватого.</li><li><a href='https://csrc.nist.gov/pubs/sp/800/61/r3/final' target='_blank' rel='noopener noreferrer'>NIST SP 800-61 Rev. 3</a> — актуальная рекомендация NIST, опубликованная в апреле 2025 года, по incident response в контексте кибербезопасности; это дополнительная рамка, а не источник фактов об учебном прикладном баге.</li></ul>"
}