| name | systematic-debugging |
| description | 在遇到任何 bug、测试失败或非预期行为时使用,在提出修复方案之前 |
系统化调试(Systematic Debugging)
概述
随意的修复浪费时间,还会引入新 bug。临时补丁只会掩盖深层问题。
核心原则: 在尝试修复之前,永远先找到根因(root cause)。修复症状即是失败。
违背本流程的字面规定,就是违背调试的精神。
铁律(The Iron Law)
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
(未先做根因调查,不得动手修复)
如果你还没完成阶段 1,就不能提出修复方案。
何时使用
适用于任何技术问题:
- 测试失败
- 生产环境 bug
- 非预期行为
- 性能问题
- 构建失败
- 集成问题
尤其要在以下情况使用:
- 处于时间压力下(紧急情况让人忍不住去猜)
- “只是一个快速修复”看起来显而易见
- 你已经尝试过多次修复
- 上一个修复没有奏效
- 你并未完全理解这个问题
不要在以下情况跳过流程:
- 问题看起来很简单(简单的 bug 同样有根因)
- 你赶时间(仓促保证返工)
- 经理希望立刻修好(系统化比来回折腾更快)
四个阶段
你必须完成每个阶段,才能进入下一个。
阶段 1:根因调查
在尝试任何修复之前:
-
仔细阅读错误信息
- 不要跳过 error 或 warning
- 它们往往直接包含了解决方案
- 完整阅读 stack trace
- 记下行号、文件路径、错误码
-
稳定地复现
- 你能可靠地触发它吗?
- 确切的步骤是什么?
- 每次都会发生吗?
- 如果无法复现 → 收集更多数据,不要猜
-
检查近期改动
- 什么改动可能导致了这个问题?
- Git diff、近期 commit
- 新增依赖、配置变更
- 环境差异
-
在多组件系统中收集证据
当系统包含多个组件时(CI → build → signing,API → service → database):
在提出修复之前,先加入诊断埋点(diagnostic instrumentation):
For EACH component boundary:
- Log what data enters component
- Log what data exits component
- Verify environment/config propagation
- Check state at each layer
Run once to gather evidence showing WHERE it breaks
THEN analyze evidence to identify failing component
THEN investigate that specific component
示例(多层系统):
echo "=== Secrets available in workflow: ==="
echo "IDENTITY: ${IDENTITY:+SET}${IDENTITY:-UNSET}"
echo "=== Env vars in build script: ==="
env | grep IDENTITY || echo "IDENTITY not in environment"
echo "=== Keychain state: ==="
security list-keychains
security find-identity -v
codesign --sign "$IDENTITY" --verbose=4 "$APP"
这能揭示: 哪一层出了问题(secrets → workflow ✓,workflow → build ✗)
-
追踪数据流
当错误深藏在调用栈中时:
完整的反向追踪技术见本目录下的 root-cause-tracing.md。
简版:
- 错误值最初从哪里产生?
- 是什么把这个错误值传进来的?
- 持续向上追踪,直到找到源头
- 在源头修复,而非在症状处修复
阶段 2:模式分析
修复前先找到模式(pattern):
-
找到可工作的范例
- 在同一代码库中定位类似的、能正常工作的代码
- 与出问题的代码相似、但能正常工作的是什么?
-
对照参考实现
- 如果要实现某个模式,请完整阅读参考实现
- 不要略读——逐行阅读
- 在应用之前彻底理解这个模式
-
找出差异
- 能工作的与出问题的之间有什么不同?
- 列出每一处差异,无论多小
- 不要假设“那个不可能有影响”
-
理解依赖
- 它还需要哪些其它组件?
- 需要哪些设置、配置、环境?
- 它做了哪些假设?
阶段 3:假设与验证
科学方法:
-
形成单一假设
- 明确陈述:“我认为 X 是根因,因为 Y”
- 把它写下来
- 要具体,不要含糊
-
以最小改动验证
- 做出尽可能最小的改动来验证假设
- 一次只改一个变量
- 不要一次修多个东西
-
继续之前先验证
- 奏效了吗?是 → 阶段 4
- 没奏效?形成新的假设
- 不要在原有修复之上再叠加修复
-
当你不知道时
- 说“我不理解 X”
- 不要假装知道
- 寻求帮助
- 进一步研究
阶段 4:实现
修复根因,而非症状:
-
创建会失败的测试用例
- 尽可能最简单的复现
- 尽量是自动化测试
- 若没有测试框架,写一次性的测试脚本
- 修复前必须先有它
- 使用
superpowers:test-driven-development 技能来编写规范的失败测试
-
实现单一修复
- 针对已识别的根因
- 一次只做一个改动
- 不要顺手做“既然来了”式的改进
- 不要捆绑重构
-
验证修复
- 测试现在通过了吗?
- 没有破坏其它测试吧?
- 问题真的解决了吗?
-
如果修复不奏效
- 停下
- 数一数:你已经尝试了多少次修复?
- 如果 < 3:回到阶段 1,带着新信息重新分析
- 如果 ≥ 3:停下,质疑架构(见下方第 5 步)
- 不要在没有架构层面讨论的情况下尝试第 4 次修复
-
如果 3 次以上修复都失败:质疑架构
表明架构存在问题的征兆:
- 每次修复都在不同的地方暴露出新的共享状态/耦合/问题
- 修复需要“大规模重构”才能实现
- 每次修复都在别处引出新症状
停下,质疑根本前提:
- 这个模式本身是否站得住脚?
- 我们是否只是“出于惯性在硬撑”?
- 应该重构架构,还是继续修复症状?
在尝试更多修复之前,先与你的人类搭档讨论
这不是一个失败的假设——这是一个错误的架构。
危险信号——停下并遵循流程
如果你发现自己冒出以下念头:
- “先快速修一下,之后再调查”
- “就改改 X 看看好不好使”
- “一次加多个改动,跑测试”
- “跳过测试,我手动验证一下”
- “大概是 X,我把它修了”
- “我没完全理解,但这也许能行”
- “模式说的是 X,但我会换个方式套用它”
- “这是主要问题:[未经调查就列出修复方案]”
- 在追踪数据流之前就提出解决方案
- “再修一次试试”(在已尝试 2 次以上时)
- 每次修复都在不同地方暴露新问题
以上所有都意味着:停下。回到阶段 1。
如果 3 次以上修复都失败: 质疑架构(见阶段 4 第 5 步)
你的人类搭档发出的“你做错了”信号
留意这些纠偏话语:
- “是没发生吗?” —— 你没验证就做了假设
- “它会显示给我们……吗?” —— 你本应加入证据收集
- “别猜了” —— 你在没理解的情况下提出修复
- “Ultrathink 一下” —— 质疑根本前提,而非只看症状
- “我们卡住了?”(带着挫败感) —— 你的方法不奏效
当你看到这些时: 停下。回到阶段 1。
常见的自我合理化(Rationalizations)
| 借口 | 现实 |
|---|
| “问题简单,不需要走流程” | 简单问题也有根因。流程对简单 bug 也很快。 |
| “紧急情况,没时间走流程” | 系统化调试比来回猜测折腾更快。 |
| “先试这个,然后再调查” | 第一个修复就定下了基调。从一开始就做对。 |
| “我会在确认修复有效后再写测试” | 未经测试的修复站不住脚。先写测试才能证明它有效。 |
| “一次修多个能省时间” | 无法隔离出哪个起了作用。会引发新 bug。 |
| “参考实现太长,我就改改模式套用” | 一知半解必出 bug。要完整读完。 |
| “我看到问题了,让我修了它” | 看到症状 ≠ 理解根因。 |
| “再修一次试试”(在 2 次以上失败后) | 3 次以上失败 = 架构问题。质疑模式,别再修了。 |
速查表
| 阶段 | 关键活动 | 成功标准 |
|---|
| 1. 根因 | 读错误、复现、查改动、收集证据 | 理解“是什么”和“为什么” |
| 2. 模式 | 找可工作的范例、对照比较 | 找出差异 |
| 3. 假设 | 形成理论、以最小改动验证 | 已确认,或形成新假设 |
| 4. 实现 | 创建测试、修复、验证 | bug 解决,测试通过 |
当流程显示“没有根因”时
如果系统化调查显示问题确实属于环境性、时序依赖性或外部性:
- 你已经完成了流程
- 记录下你调查了什么
- 实现合适的处理(重试、超时、错误信息)
- 加入监控/日志以便日后调查
但是: 95% 的“没有根因”案例其实是调查不彻底。
配套技术
以下技术是系统化调试的一部分,可在本目录中找到:
root-cause-tracing.md —— 沿调用栈反向追踪 bug,找到最初的触发点
defense-in-depth.md —— 找到根因后,在多个层次加入校验
condition-based-waiting.md —— 用条件轮询替代任意超时
相关技能:
- superpowers:test-driven-development —— 用于创建失败测试用例(阶段 4,第 1 步)
- superpowers:verification-before-completion —— 在宣称成功前验证修复确实奏效
实战效果
来自实际调试会话:
- 系统化方法:15–30 分钟完成修复
- 随意修复方法:2–3 小时来回折腾
- 一次修复成功率:95% vs 40%
- 引入的新 bug:几乎为零 vs 常见