Agent Dev 101

Модуль 17 · Урок 17.13

Урок 17.13. Систематическая отладка и доказательство готовности

На этой странице
  1. Зачем это нужно
  2. Основная идея
  3. Systematic-debugging: четыре фазы
  4. Техника: root-cause-tracing
  5. Техника: defense-in-depth
  6. Техника: condition-based-waiting
  7. Verification-before-completion
  8. Связь с остальным модулем
  9. Практика
  10. Краткое резюме
  11. Вопросы для самопроверки
  12. Что дальше
  13. Источники

Дополнительные источники: https://github.com/obra/superpowers/blob/main/skills/systematic-debugging/root-cause-tracing.md, https://github.com/obra/superpowers/blob/main/skills/systematic-debugging/defense-in-depth.md, https://github.com/obra/superpowers/blob/main/skills/systematic-debugging/condition-based-waiting.md, https://github.com/obra/superpowers/blob/main/skills/verification-before-completion/SKILL.md

Зачем это нужно

Сквозные дисциплины качества Superpowers (категория Debugging справочника — урок 17.5): systematic-debugging срабатывает на любой баг до предложения фиксов; verification-before-completion — перед любым заявлением «готово». Оба скилла — одно философское утверждение, четвертый пункт философии проекта (урок 17.1): evidence over claims — доказательства вместо заявлений.

Для агентов эти скиллы важнее, чем для людей: агент по природе оптимист — он «видит» код правильным, потому что писал его; догадка под давлением времени для него не отклонение, а норма. Отладка по гипотезам в курсе уже разбиралась (урок 9.5); здесь — перевод скиллов Superpowers с их техниками: трассировка корневой причины, защита в глубину, ожидание условий — и «железный закон» верификации.

Основная идея

Два железных закона:

text
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
(систематическая отладка: никаких фиксов без расследования корневой причины)

NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE
(верификация: никаких заявлений о завершении без свежих доказательств верификации)

И одинаковое вступление у обоих скиллов: «Violating the letter of this rule is violating the spirit of this rule» — нарушение буквы правила есть нарушение его духа. Это отрезает целый класс рационализаций «я следую духу, а не ритуалу» (тот же прием, что в TDD — урок 17.10).

Systematic-debugging: четыре фазы

Применяется к любому техническому вопросу: падение тестов, баги в продакшене, неожиданное поведение, проблемы производительности, падения сборки, интеграционные проблемы. Особенно — под давлением времени, когда «один быстрый фикс» кажется очевидным, уже попробовано несколько фиксов, предыдущий не сработал, проблема не до конца понятна. Каждая фаза завершается до перехода к следующей; не завершена фаза 1 — фиксы предлагать нельзя.

Фаза 1: расследование корневой причины

До любого фикса:

  1. Внимательно прочитать сообщения об ошибках — не проскальзывать мимо ошибок и предупреждений; они часто содержат точное решение; читать стек-трейсы целиком; фиксировать номера строк, пути, коды ошибок.
  2. Воспроизвести стабильно: запускается ли reliably; каковы точные шаги; происходит ли каждый раз. Невоспроизводимо — собирать больше данных, не догадаться.
  3. Проверить недавние изменения: что изменилось и могло стать причиной; git diff, недавние коммиты, новые зависимости, изменения конфигурации, отличия окружения.
  4. Собрать доказательства в мульти-компонентных системах: CI → build → signing, API → service → DB — до предложения фиксов добавить диагностическую инструментацию на каждую границу компонентов: что входит, что выходит, как распространяются env/config, состояние на каждом слое. Один прогон собирает доказательства, где ломается; потом анализ доказательств указывает компонент; потом исследуется этот компонент. Пример из скилла — послойная диагностика CI/сборки/подписи: echo секретов в workflow → env в build-скрипте → состояние keychain → сама подпись с --verbose=4; вывод показывает, какой слой ломается.
  5. Трассировать поток данных (ошибка глубоко в стеке) — полная техника в root-cause-tracing.md (раздел ниже): откуда плохое значение, кто вызвал с ним; трассировать вверх до источника; чинить в источнике, не в симптоме.

Фаза 2: анализ паттерна

До фикса — найти паттерн:

  1. Найти работающие примеры: похожий работающий код в той же кодовой базе; что работает из похожего на сломанное?
  2. Сравнить с референсами: реализуете паттерн — прочитать референсную реализацию полностью, не по диагонали: каждую строку.
  3. Определить отличия: что различается между работающим и сломанным; выписать каждое, не полагаясь на «это же не может влиять».
  4. Понять зависимости: какие еще компоненты нужны, какие настройки/конфиг/окружение, какие предположения делает код.

Фаза 3: гипотеза и проверка

Научный метод:

  1. Одна гипотеза, сформулированная ясно: «Думаю, X — корневая причина, потому что Y»; записать; конкретно, не размыто.
  2. Минимальная проверка: малейшее возможное изменение для проверки гипотезы; одна переменная за раз; не чинить несколько вещей сразу.
  3. Верифицировать до продолжения: сработало — фаза 4; не сработало — новая гипотеза; не наслаивать фиксы поверх.
  4. Когда не знаете — сказать «не понимаю X»; не притворяться; попросить помощи; исследовать дальше.

Фаза 4: реализация

Чинить корневую причину, не симптом:

  1. Падающий тест-кейс: простейшее воспроизведение; автоматический тест, одноразовый скрипт при отсутствии фреймворка; обязателен до фикса (TDD — урок 17.10).
  2. Единственный фикс: адресует корневую причину; одно изменение за раз; никаких «заодно» улучшений; никакого bundled-рефакторинга.
  3. Верифицировать фикс: тест проходит; другие тесты не сломаны; вопрос действительно решен — под verification-before-completion (раздел ниже).
  4. Фикс не сработал: СТОП; посчитать фикс-попытки. Меньше трех — назад в фазу 1 с новой информацией. Три и больше — стоп и вопрос к архитектуре (пункт 5).
  5. 3+ фикса провалились — вопрос к архитектуре: паттерн (каждый фикс вскрывает новое общее состояние/связку/проблему в другом месте; фиксы требуют «массированного рефакторинга»; каждый фикс создает новые симптомы) — это не провалившаяся гипотеза, это неправильная архитектура. Остановиться и спросить: паттерн в принципе здравый? держимся за него по инерции? рефакторить архитектуру вместо продолжения затыкания симптомов? Обсудить с партнером до новых попыток.

Красные флаги

Поймали себя на: «быстрый фикс сейчас, расследование потом»; «попробую поменять X и посмотрим»; «несколько изменений сразу и прогон тестов»; «пропущу тест, проверю руками»; «наверняка X, поправлю это»; «не до конца понимаю, но может сработать»; «вот основные проблемы: [список фиксов без расследования]»; предложения решений до трассировки данных; «еще одна попытка фикса» (когда уже 2+); каждый фикс вскрывает новую проблему в новом месте — все это означает: СТОП, вернуться в фазу 1; при 3+ провалах — вопрос к архитектуре.

Техника: root-cause-tracing

Баги часто проявляются глубоко в стеке вызовов (git init в неправильном каталоге, файл не там, БД не с тем путем) — инстинкт чинить там, где ошибка видна, лечит симптом. Принцип: трассировать назад по цепочке вызовов до оригинального триггера, чинить в источнике.

Процесс (пример скилла, переведенный):

  1. Симптом: Error: git init failed in ~/project/packages/core.
  2. Непосредственная причина: await execFileAsync('git', ['init'], { cwd: projectDir }).
  3. Кто вызвал?: WorktreeManager.createSessionWorktree(projectDir, sessionId) ← Session.initializeWorkspace() ← Session.create() ← тест в Project.create().
  4. Какое значение передано?: projectDir = '' — пустая строка! Пустая строка как cwd резолвится в process.cwd() — каталог исходников.
  5. Оригинальный триггер: const context = setupCoreTest(); // возвращает { tempDir: '' } — тест обратился к context.tempDir до beforeEach.

Нельзя трассировать руками — добавить инструментацию: логировать до опасной операции (не после падения), в тестах — console.error (не logger, его могут гасить), с контекстом (каталог, cwd, env, таймстампы) и new Error().stack (полная цепочка вызовов). Неизвестно, какой тест «загрязняет» прогон — бисекция: скрипт find-polluter.sh запускает тесты по одному, останавливаясь на первом «загрязнителе».

Итог примера: корневая причина — top-level инициализация с пустым значением; фикс — геттер, бросающий при обращении до beforeEach; плюс защита в глубину (следующая техника).

Техника: defense-in-depth

Исправив баг плохих данных, добавить валидацию в одном месте кажется достаточным — но одиночная проверка обходится другими путями кода, рефакторингом и моками. Принцип: валидировать на каждом слое, через который проходят данные; сделать баг структурно невозможным. Разница: одиночная проверка — «мы починили баг»; слои — «мы сделали баг невозможным». Разные слои ловят разное: входная валидация — большинство багов; бизнес-логика — краевые случаи; охранные проверки окружения — контекстные опасности; debug-логирование — форензика, когда остальное не сработало.

Четыре слоя (по документу):

  1. Вход: отвергнуть очевидно некорректное на границе API (пустое → throw; не существует → throw; не каталог → throw).
  2. Бизнес-логика: данные имеют смысл для операции (projectDir обязателен для инициализации workspace).
  3. Стражи окружения: отказ от опасных операций в контексте (в тестах — NODE_ENV=test — git init отказывается работать вне tmpdir).
  4. Диагностическая инструментация: лог контекста со стеком до опасной операции.

Итог примера из скилла: 1847 тестов прошли, баг невозможно воспроизвести; и — ключевая мысль — все четыре слоя оказались необходимы: в тестировании каждый слой ловил баги, которые пропускали остальные: разные пути обходили вход; моки обходили логику; краевые случаи на других платформах ловили стражи; лог вскрывал структурное злоупотребление. Не останавливаться на одной точке валидации.

Техника: condition-based-waiting

Flaky-тесты угадывают время произвольными задержками — гонки, где на быстрых машинах проходит, под нагрузкой и в CI падает. Принцип: ждать фактическое условие, а не догадку о длительности.

typescript
// ❌ ДО: угадывание времени
await new Promise(r => setTimeout(r, 50));
const result = getResult();

// ✅ ПОСЛЕ: ожидание условия
await waitFor(() => getResult() !== undefined);

Быстрые паттерны: waitFor(() => events.find(e => e.type === 'DONE')) — событие; waitFor(() => machine.state === 'ready') — состояние; waitFor(() => items.length >= 5) — количество; waitFor(() => fs.existsSync(path)) — файл. Ошибки: слишком быстрый поллинг (разумно — 10 мс); без таймаута (вечный цикл; всегда таймаут с ясной ошибкой); устаревшие данные (геттер — внутри цикла). Когда произвольная задержка правильна: тестируется само время (debounce, интервалы) — и тогда с комментарием, почему (пример скилла: 200 мс = 2 тика по 100 мс, с комментарием). Итог из реальной сессии: 15 flaky-тестов в 3 файлах, 60% → 100% проход, выполнение на 40% быстрее.

Verification-before-completion

Второй скилл пары — ворота на любые заявления о статусе. Закон: «If you haven't run the verification command in this message, you cannot claim it passes» — не запустили команду верификации в этом сообщении — не можете утверждать, что она проходит.

Функция-ворота:

text
ДО заявления любого статуса или удовлетворения:

1. IDENTIFY: какая команда доказывает это утверждение?
2. RUN: выполнить ПОЛНУЮ команду (свежую, целиком)
3. READ: полный вывод, код выхода, посчитать падения
4. VERIFY: вывод подтверждает утверждение?
   нет → назвать фактический статус с доказательствами
   да → назвать утверждение С доказательствами
5. ТОЛЬКО ТОГДА: заявить

Пропуск любого шага = ложь, не верификация

Таблица требований из скилла — что заявлению недостаточно:

Заявление Требует Недостаточно
«Тесты проходят» вывод команды: 0 падений прошлый прогон, «должны пройти»
«Линтер чист» вывод линтера: 0 ошибок частичная проверка, экстраполяция
«Сборка проходит» exit 0 линтер чист, «логи выглядят хорошо»
«Баг исправлен» исходный симптом проходит тест «код изменен, наверное исправлено»
«Агент завершил» VCS-дифф показывает изменения агент отчитался «success»
«Требования выполнены» построчный чек-лист «тесты проходят»

Красные флаги скилла: слова «должно», «наверняка», «кажется»; выражение удовлетворения до верификации («Great!», «Perfect!», «Done!»); коммит/push/PR без верификации; доверие отчетам агентов об успехе; частичная верификация; «только этот раз»; усталость; любая формулировка, подразумевающая успех без запуска верификации.

Ключевые паттерны: регрессионный тест верифицируется циклом RED-GREEN («написал регрессионный тест» без проверки, что он падает до фикса и проходит после, — не верифицирован); делегирование агенту — «агент отчитался об успехе → проверить VCS-дифф → верифицировать изменения → отчитаться о фактическом состоянии»; требования — «перечитать план → чек-лист → проверить каждый → отчитаться о пробелах или завершении», а не «тесты проходят, фаза завершена».

Связь с остальным модулем

  • TDD (урок 17.10): фаза 4 отладки — тот же цикл RED-GREEN; «нашли баг → падающий тест → фикс»; фиксы без теста запрещены обоими скиллами.
  • Исполнение планов (урок 17.9): шаг не совпал с Expected: → код неверен = systematic-debugging, не подгонка вывода; контракт завершения задачи — под verification-before-completion.
  • Ревью (урок 17.11): находки ревьюера проверяются по правилам receiving — «проверить до исполнения» — тот же принцип.
  • Диагностика Superpowers (урок 17.14): когда сессия пошла не так, diagnosing-скилл применяет ту же дисциплину к самому Superpowers — каждый вывод с path:line.

Практика

  1. Возьмите учебный баг (или реальный из своего проекта) и проведите четыре фазы письменно: симптом → непосредственная причина → цепочка вызовов вверх → оригинальный триггер. Проверьте: чините источник или симптом?
  2. Для фикса спланируйте защиту в глубину: какие четыре слоя стоят на пути плохого значения в вашем случае?
  3. Сформулируйте для фикса верификацию по функции-воротам: какая команда доказывает «исправлено»? Что вы прочитаете в выводе? Что нельзя заявлять до этого?

Краткое резюме

Пара скиллов — одна философия «evidence over claims»: systematic-debugging запрещает фиксы без корневой причины (четыре фазы: расследование → паттерн → гипотеза → реализация; 3+ провала — вопрос к архитектуре), verification-before-completion — заявления без свежей верификации.

  • Трассировка корневой причины: назад по стеку до оригинального триггера; чинить источник; инструментация — до опасной операции.
  • Защита в глубину: валидация на каждом слое — вход, логика, стражи окружения, лог; все слои необходимы, «баг невозможен» вместо «баг починен».
  • Ожидание условий: waitFor(условие) вместо задержек; задержка — только с обоснованием, почему.
  • Функция-ворота верификации: определить команду → запустить → прочитать → сверить → заявить; пропуск шага — ложь, не верификация.

Вопросы для самопроверки

  1. Почему железный закон отладки стоит до предложений фиксов? Какие рационализации он отрезает?
  2. Опишите четыре фазы. Что происходит после трех провалившихся фиксов и почему это «вопрос к архитектуре», а не гипотеза?
  3. В чем разница «починили баг» и «сделали баг невозможным»? Почему все четыре слоя защиты оказались необходимы в примере скилла?
  4. Когда произвольная задержка в тесте правильна? Какие три требования сопровождают этот случай?
  5. Перечислите шаги функции-вороты верификации. Почему доверие отчету агента об успехе — красный флаг?
  6. Как «нарушение буквы есть нарушение духа» работает против аргумента «я следую духу, а не ритуалу»?

Что дальше

Отладка и верификация дисциплинируют код; следующий урок, 17.14, применяет ту же строгость к самому Superpowers: скилл diagnosing-superpowers — что делать, когда сессия пошла не так, и как собрать доказательства с построчными ссылками.

Источники

Обновлено 03.10.2026