系统化调试
概述
随机修复会浪费时间并制造新的 bug。快速补丁会掩盖底层问题。
核心原则: 尝试修复之前,始终先找到根本原因。修症状就是失败。
违反这个流程的字面要求,就是违反调试的精神。
铁律
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
如果你还没有完成第 1 阶段,就不能提出修复方案。
何时使用
用于任何技术问题:
- 测试失败
- 生产环境 bug
- 意外行为
- 性能问题
- 构建失败
- 集成问题
尤其在以下情况使用:
- 有时间压力(紧急情况会让猜测很诱人)
- “只做一个快速修复”看起来很明显
- 你已经尝试过多个修复
- 上一个修复没有奏效
- 你没有完全理解问题
不要在以下情况跳过:
- 问题看起来简单(简单 bug 也有根本原因)
- 你很赶时间(仓促必然导致返工)
- 经理要求现在就修好(系统化比乱试更快)
四个阶段
进入下一阶段前,你必须完成当前阶段。
第 1 阶段:根本原因调查
在尝试任何修复之前:
仔细阅读错误消息
- 不要跳过错误或警告
- 它们通常包含确切的解决方案
- 完整阅读堆栈跟踪
- 记录行号、文件路径、错误代码
稳定复现
- 你能可靠触发它吗?
- 精确步骤是什么?
- 每次都会发生吗?
- 如果无法复现 → 收集更多数据,不要猜
检查最近变更
- 哪些变更可能导致这个问题?
- Git diff、最近提交
- 新依赖、配置变更
- 环境差异
在多组件系统中收集证据
当系统有多个组件时(CI → build → signing,API → service → database):
提出修复前,添加诊断插桩:
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示例(多层系统):
# Layer 1: Workflow echo "=== Secrets available in workflow: ===" echo "IDENTITY: ${IDENTITY:+SET}${IDENTITY:-UNSET}" # Layer 2: Build script echo "=== Env vars in build script: ===" env | grep IDENTITY || echo "IDENTITY not in environment" # Layer 3: Signing script echo "=== Keychain state: ===" security list-keychains security find-identity -v # Layer 4: Actual signing codesign --sign "$IDENTITY" --verbose=4 "$APP"这会揭示: 哪一层失败(secrets → workflow ✓,workflow → build ✗)
追踪数据流
当错误位于调用栈深处时:
完整的反向追踪技术见本目录中的
root-cause-tracing.md。快速版本:
- 坏值从哪里来?
- 是谁用这个坏值调用了这里?
- 持续向上追踪,直到找到源头
- 在源头修复,而不是在症状处修复
第 2 阶段:模式分析
修复前先找到模式:
查找可工作的示例
- 在同一代码库中定位类似的可工作代码
- 与坏掉部分相似的可工作内容是什么?
与参考实现比较
- 如果要实现某个模式,完整阅读参考实现
- 不要浏览了事——阅读每一行
- 在应用前充分理解该模式
识别差异
- 可工作部分与坏掉部分有什么不同?
- 列出每一个差异,无论多小
- 不要假设“那不可能有影响”
理解依赖
- 它需要哪些其他组件?
- 需要哪些设置、配置、环境?
- 它做了哪些假设?
第 3 阶段:假设与测试
科学方法:
形成单一假设
- 清楚说明:“我认为 X 是根本原因,因为 Y”
- 把它写下来
- 要具体,不要含糊
最小化测试
- 做出尽可能小的改动来测试假设
- 一次只改一个变量
- 不要一次修复多个问题
继续前先验证
- 有效吗?是 → 第 4 阶段
- 无效?形成新的假设
- 不要在上面继续叠加修复
当你不知道时
- 说“我不理解 X”
- 不要假装知道
- 寻求帮助
- 继续研究
第 4 阶段:实现
修复根本原因,而不是症状:
创建失败测试用例
- 尽可能简单的复现
- 如可能,使用自动化测试
- 如果没有框架,就写一次性测试脚本
- 修复前必须有
- 使用
superpowers:test-driven-development技能编写正确的失败测试
实现单一修复
- 处理已识别的根本原因
- 一次只做一个变更
- 不做“顺手改进”
- 不捆绑重构
验证修复
- 测试现在通过了吗?
- 没有其他测试坏掉吗?
- 问题确实解决了吗?
如果修复无效
- 停下
- 计数:你已经尝试了多少个修复?
- 如果 < 3:回到第 1 阶段,带着新信息重新分析
- 如果 ≥ 3:停下并质疑架构(见下面第 5 步)
- 没有架构讨论,不要尝试第 4 个修复
如果 3 个以上修复失败:质疑架构
表明存在架构问题的模式:
- 每个修复都会在不同位置暴露新的共享状态/耦合/问题
- 修复需要“大规模重构”才能实现
- 每个修复都会在其他地方制造新症状
停下并质疑根本:
- 这个模式在根本上成立吗?
- 我们是否只是“纯粹靠惯性坚持它”?
- 我们应该重构架构,还是继续修症状?
尝试更多修复前,先与你的人类伙伴讨论
这不是假设失败——这是架构错误。
危险信号——停下并遵循流程
如果你发现自己在想:
- “现在先快速修复,之后再调查”
- “先试着改 X,看看是否有效”
- “加多个变更,然后跑测试”
- “跳过测试,我会手动验证”
- “可能是 X,让我修一下”
- “我没有完全理解,但这样可能有用”
- “模式说 X,但我会用不同方式改编”
- “主要问题如下:[列出未经调查的修复]”
- 在追踪数据流前提出解决方案
- “再尝试一个修复”(当已经尝试过 2 个以上时)
- 每个修复都会在不同位置暴露新问题
所有这些都意味着:停下。回到第 1 阶段。
如果 3 个以上修复失败: 质疑架构(见第 4.5 阶段)
你的人类伙伴发出的“你做错了”信号
留意这些纠偏:
- “那没有发生吗?”——你没有验证就做了假设
- “它会向我们展示……吗?”——你本该添加证据收集
- “别猜了”——你在没有理解的情况下提出修复
- “深入思考这个问题”——质疑根本,而不只是症状
- “我们卡住了吗?”(沮丧)——你的方法没有奏效
当你看到这些: 停下。回到第 1 阶段。
常见合理化借口
| 借口 | 现实 |
|---|---|
| “问题很简单,不需要流程” | 简单问题也有根本原因。对简单 bug 来说,流程很快。 |
| “紧急情况,没时间走流程” | 系统化调试比猜测-检查式乱试更快。 |
| “先试这个,然后再调查” | 第一个修复会设定模式。从一开始就做对。 |
| “确认修复有效后我再写测试” | 未经测试的修复站不住。先写测试才能证明。 |
| “一次修复多个问题能省时间” | 无法隔离到底什么起作用。还会制造新 bug。 |
| “参考太长了,我会改编这个模式” | 理解不完整必然导致 bug。完整阅读。 |
| “我看出问题了,让我修” | 看见症状 ≠ 理解根本原因。 |
| “再尝试一个修复”(2 次以上失败后) | 3 次以上失败 = 架构问题。质疑模式,不要再修。 |
快速参考
| 阶段 | 关键活动 | 成功标准 |
|---|---|---|
| 1. 根本原因 | 阅读错误、复现、检查变更、收集证据 | 理解是什么以及为什么 |
| 2. 模式 | 查找可工作示例、比较 | 识别差异 |
| 3. 假设 | 形成理论、最小化测试 | 确认假设或形成新假设 |
| 4. 实现 | 创建测试、修复、验证 | Bug 已解决,测试通过 |
当流程显示“没有根本原因”时
如果系统化调查显示问题确实是环境性、时序依赖或外部问题:
- 你已经完成流程
- 记录你调查过的内容
- 实现适当处理(retry、timeout、error message)
- 添加监控/日志,供未来调查使用
但是: 95% 的“没有根本原因”案例其实是调查不完整。
支撑技术
这些技术是 systematic debugging 的一部分,可在本目录中找到:
root-cause-tracing.md——沿调用栈向后追踪 bug,找到最初触发点defense-in-depth.md——找到根本原因后,在多层添加验证condition-based-waiting.md——用条件轮询替代任意 timeout
相关技能:
- superpowers:test-driven-development——用于创建失败测试用例(第 4 阶段,第 1 步)
- superpowers:verification-before-completion——在声称成功前验证修复有效
真实世界影响
来自调试会话:
- 系统化方法:15-30 分钟修复
- 随机修复方法:2-3 小时乱试
- 首次修复成功率:95% vs 40%
- 引入的新 bug:接近零 vs 常见