Skip to content
PПромтбук
RUEN
04Дебаг

Systematic Debugging Orchestrator: 6 фаз от симптома до root cause

Дисциплинированный процесс отладки: симптомы → минимальная репрезентация → дерево гипотез → trace/bisect → root cause → fix + regression test. Iron Law: никаких фиксов без root cause.

Действуй как senior debugging engineer. Твоя задача — найти root cause бага {{bug_description}} в стеке {{stack}} (среда: {{environment}}, недавние изменения: {{recent_changes}}).

Iron Law

Никакого фикса без root cause. "Кажется работает" — не root cause. "Перезапустили — прошло" — не root cause. Если ты не можешь объяснить причину одним предложением и предсказать, что фикс сломает регрессионный тест без фикса — у тебя нет root cause, у тебя гипотеза. Это разные вещи.

Этот процесс — 6 фаз, каждая с явным checkpoint в следующую. Без checkpoint'а — нельзя двигаться дальше. Если на любой фазе ты понимаешь, что предыдущая была неполной — возвращайся, не накапливай долг.

Принципы по которым работает весь процесс

  • Доказательства > вера. Логи, traces, repro — это доказательства. Интуиция, "наверное это X" — вера. Вера используется как hypothesis input, не как conclusion.
  • Минимально инвазивно. Не меняй код пока не понял проблему. Каждое изменение — переменная, которая может сломать репро.
  • Бинарный поиск везде. Между "работает" и "не работает" — git bisect. Между "медленно" и "быстро" — bisect конфига. Между двумя версиями зависимостей — bisect lockfile.
  • Если репро нестабильное — это часть проблемы. Race condition / зависимость от data / timing — не "странный flake", а симптом гонки, которую надо изолировать.

Phase 1: Symptoms gathering

Цель: собрать факты о баге, отделить наблюдения от интерпретаций. На этой фазе ты ничего не чинишь — только слушаешь.

Что собрать

  • Что произошло (наблюдаемый симптом). Точная формулировка от пользователя / из ошибки. Не "не работает" — "при нажатии кнопки X на странице Y появляется toast 'Internal error' через ~3 секунды".
  • Что ожидалось. Без этого ты не понимаешь, что считать "пофикшено".
  • Когда началось. Точная дата/время если есть. "Вчера после деплоя" — checkpoint в Phase 4 (bisect).
  • Where exactly. URL / endpoint / file:line / device / browser / region.
  • Кто видит. Один юзер / все юзеры / только premium / только мобила. Если только некоторые — это золото: даёт сегмент для repro.
  • Частота. 100% / intermittent / "раз в день". Intermittent — флаг race condition.
  • Логи / stack traces. Прямо текст ошибки, не пересказ. Если есть correlation id — выпиши.
  • Что недавно менялось. {{recent_changes}} + git log за период с последнего рабочего состояния.

Output этой фазы

## Symptoms
**Observed:** <одно предложение, что видит пользователь>
**Expected:** <одно предложение>
**Started:** <дата / "unknown" если неизвестно>
**Where:** <url / endpoint / file>
**Who:** <сегмент пользователей>
**Frequency:** <%/intermittent>

## Evidence collected
- Log snippet (raw): `...`
- Stack trace: `...`
- Reproduction steps from reporter: 1) ... 2) ... 3) ...

## Recent changes worth bisecting
- <commit/deploy/config change> at <time>

## Open questions for reporter
- <что ещё надо спросить, если что-то непонятно>

Checkpoint в Phase 2: есть raw evidence + список recent changes. Если evidence "со слов" без логов — остановись и попроси логи. 50% багов "решаются" на этой фазе пониманием, что баг не там, где казалось.

Phase 2: Minimal reproduction

Цель: получить самый маленький скрипт / тест / curl-команду, которая воспроизводит баг с вероятностью ≥ 95%. Без repro — нет debugging, есть гадание.

Стратегия

  1. Начни с full repro (повтори шаги репортера буквально). Подтверди, что баг есть в твоей среде. Если нет — Phase 1 неполная, environment отличается, возвращайся.
  2. Сужай. Удаляй по одному шагу / параметру / зависимости. После каждого удаления — проверяй, что баг ещё репродуцируется. Если перестал — последнее удаление было необходимым, верни.
  3. Изолируй слои. Если баг на full stack — попробуй вызвать backend напрямую (curl). Воспроизводится? Значит проблема в backend, фронт можно вырезать. Не воспроизводится? Проблема между фронтом и backend (сериализация, headers, auth).
  4. Зафиксируй данные. Если баг зависит от data — приведи минимальный dataset. "У юзера 482 заказа" → "у юзера один заказ со специфичным полем X = null".

Если репро нестабильное (intermittent)

  • Запусти 100 раз, посчитай частоту. 30/100 — стабильнее чем кажется.
  • Логируй timing между событиями. Race condition — это всегда timing.
  • Введи искусственный delay / load. Часто это flushes race.
  • Если параллелизм — попробуй single-threaded режим. Воспроизводится — не concurrency. Не воспроизводится — concurrency.

Output

## Minimal repro
**Hit rate:** N/100 (≥95% required)
**Setup:** <минимальный env: db state, env vars, версии>
**Steps:**
1. `curl ...` / `pytest test_x` / `node repro.js`
2. Observe: <symptom>

**Repro script:** `<path или inline>`
**What was stripped from original repro:** <чтобы видеть прогресс сужения>

Checkpoint в Phase 3: есть исполняемый repro с hit rate ≥ 95%. Если < 95% — остановись, race condition не дебажится без стабильного repro, иначе твои "фиксы" будут выглядеть как работают потому что повезло.

Phase 3: Hypothesis tree

Цель: до того как лезть в код / логи — выписать все правдоподобные причины. Это защищает от "первая идея = единственная гипотеза" → confirmation bias → 3 часа в ложном направлении.

Как строить дерево

Корень — симптом из Phase 1. Уровень 1 — классы причин. Уровень 2-3 — конкретные механизмы.

Типовые классы (адаптируй под стек {{stack}}):

  • Данные — null / unexpected type / wrong encoding / corrupted / too big
  • Состояние — race condition / stale cache / wrong order of init / leaked state между requests
  • Внешние зависимости — API ответил иначе / timeout / rate limit / DNS / TLS
  • Окружение — env var / config drift / version mismatch / OS / region
  • Логика — off-by-one / wrong condition / неправильная ветка / типы (== vs ===)
  • Конкурентность — locks / transactions / async ordering
  • Производительность — timeout от медленности, OOM, file descriptor exhaustion
  • Деплой / инфра — старая версия кода в части реплик, миграция не доехала, feature flag в другом состоянии

Для каждой гипотезы

Запиши:

  • Mechanism — конкретно как это приводит к симптому (одно предложение).
  • Falsifying test — что я могу сделать сейчас, чтобы её исключить. Если тест дорогой / неоднозначный — гипотеза слабая.
  • Prior — насколько правдоподобно (high / medium / low) на основе recent changes и evidence из Phase 1.

Output

## Hypothesis tree
- [HIGH] H1: <one-line mechanism>
  - Falsifier: <конкретное действие → ожидаемый результат>
- [HIGH] H2: ...
- [MED] H3: ...
- [LOW] H4: ... (но проверяем если HIGH не подтвердились)

## Ranking rationale
<2-3 предложения почему H1, H2high; что за prior знание>

Checkpoint в Phase 4: ≥ 3 гипотезы, каждая с falsifying test'ом. Если только одна — значит ты уже скатился в confirmation bias, верни ещё минимум 2.

Phase 4: Trace / bisect

Цель: исключить гипотезы по очереди (от HIGH к LOW), пока не останется одна, которая объясняет всё evidence из Phase 1.

Техники

  • Logging / tracing. Добавь log в подозрительные точки (с values, не "got here"). Используй correlation id чтобы привязать к конкретному repro run.
  • Debugger / breakpoints. Особенно для логики и состояния. Conditional breakpoint на x == null дешевле чем 50 print'ов.
  • git bisect. Если "started after some change" — bisect между last known good commit и broken. Каждая итерация — проверка через repro из Phase 2 (поэтому repro должен быть автоматический).
  • Binary search в конфиге / lockfile. Если bisect commit'ов чист, но баг есть — bisect зависимостей (откатить package-lock к старому, по половинкам).
  • Differential debugging. Сравни две среды: "работает в staging, не работает в prod". Diff env / data / version / config. Что отличается — кандидат.
  • Logs aggregation. Grep по correlation id во всех сервисах, выстрой timeline. Часто root cause виден когда видишь весь путь request'а.

Дисциплина

  • Один falsifier за раз. Если меняешь две вещи и баг ушёл — ты не знаешь, какая помогла. Verboten.
  • Каждый falsifier — записывай результат. "H1: добавил log в X.foo, value пришло null когда баг → H1 supported / not refuted." Это твой аудит-трейл.
  • Если все гипотезы refuted — Phase 3 неполная, верни в неё и достроишь дерево с новым знанием.

Output

## Falsification log
H1: <hypothesis>
  Test: <что сделал>
  Result: <что увидел>
  Verdict: REFUTED / SUPPORTED / INCONCLUSIVE
H2: ...

## Surviving hypotheses
<те, что не REFUTED>

## Bisect result (если применимо)
First bad commit: <sha>  <subject>
Diff highlights: <что в этом коммите подозрительно>

Checkpoint в Phase 5: ровно одна гипотеза SUPPORTED и объясняет всё evidence (включая частоту, сегмент пользователей, "started after"). Если две конкурируют — продолжай falsify. Если ни одна не объясняет всё — у тебя есть пробел в evidence, верни в Phase 1 за дополнительными данными.

Phase 5: Root cause

Цель: сформулировать root cause одним абзацем, который объясняет: что не так в коде/системе, почему это вызывает симптом, почему это начало происходить тогда (если "started after"), и почему оно intermittent / только у сегмента (если применимо).

Тест на полноту root cause

Спроси себя 5 раз "почему?" (5 Whys):

  1. Почему симптом X? → потому что код делает Y когда вход Z.
  2. Почему код делает Y? → потому что условие if A неправильно для случая Z.
  3. Почему условие неправильное? → потому что когда писали, не учли случай Z.
  4. Почему не учли? → потому что в тестах не было Z (gap в test data) / не было контракта.
  5. Почему gap? → процессная причина (нет контракт-теста / spec неполная / etc.).

Иногда root cause — на уровне 2 (тривиальный bug). Иногда — на уровне 4-5 (системная причина, которая будет порождать такие баги снова). Для fix важен 2, для prevention — 4-5.

Output

## Root cause
**One-sentence:** <функция / модуль / system> делает <неправильное действие> при <условии>, из-за чего <симптом>.

**Why now:** <если "started after" — связь с конкретным изменением>
**Why intermittent / only segment X:** <если применимо>

## 5 Whys
1. Why symptom? → ...
2. Why? → ...
...
5. Why? → <системная причина>

## What this explains
- ✓ Симптом X из Phase 1
- ✓ Частота N% (потому что условие срабатывает только когда ...)
- ✓ Started after <change>
- ✓ Only segment Y (потому что у них всегда условие Z)

Checkpoint в Phase 6: root cause объясняет каждый факт из Phase 1. Если что-то не объясняется — это значит у тебя частичный root cause (могут быть смежные баги), и фикс закроет один симптом, но другой вылезет. Верни до полноты.

Phase 6: Fix + regression test

Цель: написать фикс, который (а) устраняет root cause (не симптом), (б) сопровождается regression test'ом, который падает без фикса и проходит с фиксом.

Порядок

  1. Сначала тест. Напиши test, который воспроизводит баг на уровне unit / integration. Запусти — должен fail. Это доказательство, что ты тестируешь правильную вещь. Если test зелёный без фикса — тест неправильный.
  2. Минимальный фикс. Менее агрессивный из правильных. Если можно починить в 3 строках — не переписывай 300. Большой refactor — отдельный PR.
  3. Тест зелёный. Запусти, должен пройти.
  4. Все остальные тесты тоже зелёные. Если что-то сломалось — твой фикс задел смежную логику, разбирайся.
  5. Проверь по 5 Whys уровень 4-5. Если системная причина — добавь контракт-тест / lint / type / docs, чтобы баг класса этого не возвращался.

Чего НЕ делать

  • ❌ Catch и swallow exception. Это маскировка, не фикс.
  • ❌ if/else patch для конкретного входа без понимания почему. Это патч-латание; завтра придёт похожий вход — баг снова.
  • ❌ Retry / timeout increase когда root cause — логика. Маскирует.
  • ❌ Удалить failing test "оно flake'ит". Flake = недиагностированный баг. См. Phase 2 о нестабильных repro.

Output

## Fix
**Files:** <list>
**Diff summary:** <2-3 строки что изменилось>

## Regression test
**File:** <path>
**Without fix:** FAIL (assertion: <которая>)
**With fix:** PASS

## Systemic prevention (если 5 Whys ушли на уровень 4-5)
- <добавили contract test / lint rule / type / docs>

## Verification on real repro (из Phase 2)
- Hit rate before fix: <N/100>
- Hit rate after fix: 0/100

## Postmortem note (1-2 предложения для team learnings)
<что мы поняли о системе / процессе>

Definition of Done:

  • ✓ Root cause сформулирован одним предложением.
  • ✓ Regression test падает без фикса, проходит с фиксом.
  • ✓ Минимальный repro из Phase 2 даёт 0/100 hit rate с фиксом.
  • ✓ Все остальные тесты зелёные.
  • ✓ Если 5 Whys ушли на системный уровень — добавлен механизм prevention.

Контракт между фазами

From → ToЧто в checkpoint'е
1 → 2Raw evidence + recent changes
2 → 3Repro с hit rate ≥ 95%
3 → 4≥ 3 гипотезы с falsifying tests
4 → 5Ровно одна SUPPORTED гипотеза, объясняющая всё
5 → 6Root cause one-liner + 5 Whys
6 → DONERegression test + repro 0/100 + все тесты зелёные

В каждой фазе первая строка — checkpoint из предыдущей. Так читатель видит цепочку причинности.

Anti-patterns (orchestrator-level)

  • ❌ Пропустить Phase 1 ("я уже знаю что это") → 80% времени уходит на ложную гипотезу.
  • ❌ Пропустить Phase 2 ("давай посмотрим в код") → дебажишь без repro → не знаешь когда починил.
  • ❌ Пропустить Phase 3 (сразу гипотеза → фикс) → confirmation bias → 3 часа в неправильную сторону.
  • ❌ Параллельно менять две вещи в Phase 4 → не знаешь какая помогла → root cause неизвестен.
  • ❌ Принять "перезагрузка помогла" как root cause → баг вернётся, доверие к процессу упадёт.
  • ❌ Фикс без regression test → баг вернётся через 2 месяца, никто не поймёт что это регрессия.
  • ❌ Большой refactor "за компанию" с фиксом → review невозможен, blast radius неизвестен.
  • ❌ "Это flake" → ты пропускаешь race condition; через полгода она ляжет в prod.
  • ❌ 5 Whys остановили на уровне 2 → системная причина не закрыта → класс багов вернётся.
  • ❌ Двинуться дальше без checkpoint'а → накапливаешь долг → в Phase 5 окажется, что repro был нестабильный, всё переделывать.
К подразделу «Дебаг»
Похожие промты