Files
progcode/editorial/agent-rewrites/253.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
14 KiB
JSON

{
"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>Восстановление сервиса и устранение причины — разные события. Временное действие должно иметь условие запуска, ожидаемый результат и путь отмены. Отдельно нужно записать тест или контракт, который не даст ошибке вернуться. Иначе отчёт сохраняет уверенность, но не сохраняет способ проверки.</p>\n<h2>Граница примера</h2>\n<p>Рассмотрим только переход от mapper к границе <code>pricing-adapter</code>. Это учебная fixture в памяти. Она не обращается к сети, базе, очереди или реальному API. Вход содержит сумму и не содержит валюту. Новый mapper возвращает объект без <code>currency</code>. Затем <code>preview-order</code> отвечает <code>blocked</code>. Мы проверяем форму рассуждения, а не заявляем production-результат.</p>\n<pre><code>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 };</code></pre>\n<p>Код намеренно короткий. Он показывает место, где исчезает значение. Он не доказывает, что именно mapper стал единственной причиной отказа. Для такого вывода нужны наблюдения до и после границы, а также проверка альтернативных причин.</p>\n<h2>Механизм: факт, гипотеза, действие, проверка</h2>\n<p>Наблюдение описывает то, что можно увидеть. «После mapper поле отсутствует» — наблюдение. «Новый mapper не переносит поле» — гипотеза. Она связывает два факта, но остаётся изменяемой. Если поле исчезло раньше, гипотеза не выдержит проверки, а факты останутся полезными.</p>\n<p>Действие должно менять одну понятную переменную. В примере это возврат к прежнему mapper на ограниченной ветке. Проверка должна измерять именно это действие: fixture получает тот же вход, возвращает <code>allowed</code> и сохраняет <code>currency</code>. Такая проверка не доказывает исправность всех заказов. Она подтверждает только заданный сценарий.</p>\n<p>Профилактика отвечает на другой вопрос: что поймает повторение? Здесь нужен контрактный тест, который отклоняет результат mapper без обязательного поля. Пока тест не написан и не прошёл, профилактика остаётся предложением. Статус <code>planned</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></tr></thead><tbody><tr><td><code>preview-order</code> вернул <code>blocked</code></td><td>Нарушен контракт обязательного поля или сработала другая ветка отказа</td><td>Сохранить ответ и проверить вход на границе mapper</td><td>Не менять retry и timeout до локализации причины</td></tr><tr><td>После mapper нет <code>currency</code></td><td>Новый mapper мог отбросить поле</td><td>Сравнить вход, результат нового mapper и результат прежнего</td><td>Сформулировать узкую гипотезу H1</td></tr><tr><td>Прежний mapper даёт <code>allowed</code></td><td>Обход возвращает известный контракт, но причина не устранена</td><td>Повторить fixture на том же входе и проверить сохранение поля</td><td>Оставить обход ограниченным и записать условие отмены</td></tr><tr><td>Проверка обхода не прошла</td><td>Гипотеза неполна или отказ вызван другой границей</td><td>Собрать новое наблюдение до следующего изменения</td><td>Отменить вывод и проверить альтернативную ветку</td></tr><tr><td>Ошибка возвращается после изменения mapper</td><td>Нет автоматической защиты контракта</td><td>Запустить тест на обязательное поле в точке передачи</td><td>Добавить контрактный тест и связать его с готовностью</td></tr></tbody></table></div>\n<h2>Почему нельзя лечить все симптомы сразу</h2>\n<p>Увеличение timeout скрывает медленный ответ, но не возвращает пропавшее поле. Дополнительный retry повторяет тот же неверный объект и может умножить побочный эффект. Одновременная правка 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>. Это не повод назвать прежний код неисправным. Новый факт говорит лишь о том, что выбранный обход не подтвердил гипотезу. Поле могло отсутствовать уже во входе. Отказ мог зависеть от другой обязательной величины. Вторая проверка должна отличить эти варианты.</p>\n<p>Хороший разбор не прячет отрицательный результат. Он сохраняет его рядом с условием, при котором проверка должна была пройти. Так следующий инженер не повторит тот же обход вслепую. Формулировка «A1 не подтвердил H1 на входе E1» полезнее, чем «фикс не сработал»: первая фраза указывает границу нового исследования.</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>Проверить результат только в пределах входа и среды. Не переносить его на неизвестные пути.</li><li>Добавить профилактику с отдельным критерием: тест должен падать на результате mapper без <code>currency</code>.</li><li>Закрыть работу только после проверки действия и профилактики. Если проверка отрицательна, начать новый цикл с наблюдения.</li></ol>\n<h2>Ограничения</h2>\n<p>Fixture не измеряет доступность, нагрузку, время восстановления, права доступа или поведение внешней зависимости. Она не заменяет журнал, мониторинг, резервирование и процедуру отката. Asset на схеме объясняет последовательность, но не является доказательством результата. Учебный код ограничен одним объектом и одним контрактом.</p>\n<p>Нельзя объявлять production-инцидент исправленным по одному успешному примеру. В реальной системе нужно подтвердить границу на фактическом запросе, проверить безопасный rollout и посмотреть на отрицательные случаи. Нельзя также превращать разбор в поиск виноватого: смена автора mapper не объясняет, почему контракт оказался без защиты.</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> — официальное описание записи влияния, действий, причин и 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> — актуальная официальная рекомендация по incident response для кибербезопасности; её процесс нельзя механически переносить на любой прикладной баг.</li></ul>"
}