何时使用
- 测试失败
- 生产 Bug
- 意外行为
- 性能问题
- 构建失败
- 集成问题
systematic-debugging 是 Superpowers 里最“反直觉”的 Skill 之一。因为它明确抵制了一种根深蒂固的程序员习惯:
看到问题 -> 马上动手修 -> 看看能不能修好
这种模式在短期内看似效率高,但实际上:
而 systematic-debugging 的核心原则非常清晰:
在找到根本原因之前,绝对不能动手修复。修复症状就是在制造失败。
这个 Skill 把调试拆成 4 个严格阶段,每个阶段都有明确的完成标准,只有全部通过才能进入下一阶段。
graph TD
A[Phase 1: 根本原因调查] --> B{完成调查?}
B -->|否| A
B -->|是| C[Phase 2: 模式分析]
C --> D{找到模式?}
D -->|否| A
D -->|是| E[Phase 3: 假设与验证]
E --> F{假设确认?}
F -->|否| E
F -->|是| G[Phase 4: 实现修复]
G --> H[修复通过?]
H -->|否| I{修复尝试>=3?}
H -->|是| J[完成]
I -->|否| A
I -->|是| K[质疑架构]
K --> L[与人类讨论]
在尝试任何修复之前,必须先完成以下步骤:
当系统有多个组件时(CI → build → signing,API → service → database):
当错误出现在调用栈深处时,使用 root-cause-tracing.md 中的完整回溯技术:
在修复之前先找到模式:
科学方法:
修复根本原因,而不是症状:
test-driven-development skill 来写正确的失败测试表明架构问题的模式:
停下来质疑基础:
在尝试更多修复之前与人类伙伴讨论
这不是假设失败——这是错误的架构。
何时使用
尤其要使用
关键要点
常见错误
| 英文术语 | 中文翻译 | 解释 |
|---|---|---|
| Root cause | 根本原因 | 导致问题的真正源头,而不是表面症状。 |
| Symptom fix | 症状修复 | 只修复问题表现,不解决真正原因。 |
| Hypothesis | 假设 | 对问题原因的具体、可测试的理论。 |
| Defense-in-depth | 纵深防御 | 在多个层添加验证,让 bug 在结构上不可能。 |
| Condition-based waiting | 条件等待 | 用条件轮询替代任意超时时间。 |
| Root cause tracing | 根本原因追踪 | 回溯调用链找到原始触发点。 |
| 错误行为 | 为什么是错的 | 正确做法 |
|---|---|---|
| ”问题很简单,不需要流程” | 简单问题也有根本原因 | 流程对简单 bug 同样快 |
| ”紧急情况,没时间走流程” | 系统化调试比猜猜改改更快 | 匆忙保证返工 |
| ”先试试这个,不行再调查” | 第一次修复就定了模式 | 从一开始就做对 |
| ”修复后再写测试” | 没测试的修复不持久 | 先写测试再修复 |
| 一次改多个东西 | 无法隔离哪个有效 | 一次只改一个 |
| 3 次修复失败后再试第 4 次 | 3+ 次失败 = 架构问题 | 停下来质疑架构 |
这个 Skill 的设计哲学非常明确:
调试不是改代码,而是理解系统。
它把调试分成 4 个阶段,每个阶段都有明确的”完成标准”,这在其他 Skill 里很少见。这种结构化不是为了复杂化,而是为了对抗人性中的一个强烈倾向:
看到错误 -> 马上动手 -> 快速尝试 -> 不行再试
这种模式短期看似高效,但长期来看会让开发者陷入:
systematic-debugging 用严格的流程来对抗这种倾向,强制你在”理解”之前不动手。
你一定见过这类情况:
systematic-debugging 的价值就是让这类”盲目调试”变成”有纪律的调查”。
错误做法:
正确做法:
错误做法:
正确做法:
错误做法:
正确做法:
测试运行失败,错误信息显示 "TypeError: undefined is not a function"
❌ 错误做法:直接搜 "undefined is not a function" 试图修复✅ 正确做法:1. 读取完整堆栈跟踪,找到报错文件行号2. 检查调用链,确认是哪个变量为 undefined3. 追溯这个变量的来源,为什么会是 undefined4. 在源头修复,而不是在报错位置打补丁线上用户反馈:提交表单后显示"服务器错误"
❌ 错误做法:随便加个 try-catch 掩盖错误✅ 正确做法:1. 查看服务器日志,找到具体错误2. 复现问题,理解触发条件3. 检查数据验证逻辑4. 添加防御性验证(单点 + 多层)同一个 Bug 修了 3 次还在出现
❌ 错误做法:再试一次不同的修复方法✅ 正确做法:1. 停下来,3 次失败 = 架构问题2. 质疑是否走错了方向3. 和人类伙伴讨论是否需要重构4. 不要用打补丁的方式掩盖架构缺陷# 1. 查看堆栈跟踪npm test 2>&1 | head -50
# 2. 查看 Git 最近变更git log --oneline -10git diff HEAD~1
# 3. 添加调试日志(临时)console.error('DEBUG:', { variable })
# 4. 追踪变量来源# 在报错位置之前添加console.trace('reached here with:', variable)
# 5. 验证修复后测试通过npm test# 确保输出:X tests passed, 0 failures| 坑 | 表现 | 解决方案 |
|---|---|---|
| ”就一个 quick fix” | 以为很小,实际很大 | 先走完整调查流程 |
| 修完就跑 | 不验证是否真的修好 | 运行完整测试套件 |
| 3 次修复失败 | 还继续试第 4 次 | 停下来质疑架构 |
| 只修表面 | 不找根本原因 | 用 root-cause-tracing 追溯 |
| 跳过验证 | 手工测一下就认为行了 | 用自动化测试验证 |
using-superpowers,用于判断当前是否该进入调试流程。test-driven-development(创建失败测试用例)、verification-before-completion(验证修复有效)。这份文档教你在调用栈深处定位问题。
核心思想:
实用技巧:
这份文档和 defense-in-depth 配合使用效果最好:先追踪到源头,再在每一层添加防御。
这份文档的核心洞见是:
单点验证 = “我们修了 bug” 多层验证 = “我们让 bug 不可能发生”
它定义了四层防御:
这个方法在修复后特别有价值,因为它把”修一个 bug”变成”让一类 bug 在结构上不可能”。
这份文档解决一个常见问题:
用任意超时时间(sleep 5 秒)来等待异步操作完成
这种做法的缺点:
正确的做法是用条件轮询:
查看源文件: GitHub原始文件