name: systematic-debugging description: | 用于调试bug、测试失败和意外行为的系统化方法。 在遇到任何技术问题并提出修复方案前使用。涵盖根本原因调查、模式分析、假设测试和修复实施。 特别适用于时间紧迫、"快速修复"看似明显或已尝试多次修复的情况。不适用于探索性代码阅读。
系统化调试
随机修复浪费时间并产生新bug。快速补丁掩盖了根本问题。
核心原则: 在尝试修复之前,始终找出根本原因。症状修复是失败的。
铁律
没有根本原因调查,就没有修复
如果你没有完成第一阶段,就不能提出修复方案。
四个阶段
第一阶段:根本原因调查
在尝试任何修复之前:
-
仔细阅读错误信息
- 不要跳过错误或警告
- 完整阅读堆栈跟踪
- 注意行号、文件路径、错误代码
-
稳定复现
- 你能可靠地触发它吗?
- 确切的步骤是什么?
- 如果无法复现,收集更多数据 - 不要猜测
-
检查近期变更
- Git diff,近期提交
- 新依赖项,配置变更
- 环境差异
-
在多组件系统中收集证据
当系统有多个组件时(CI -> 构建 -> 签名,API -> 服务 -> 数据库):
对于每个组件边界: - 记录进入组件的数据 - 记录离开组件的数据 - 验证环境/配置传播 运行一次以收集显示它在何处中断的证据 然后分析以识别故障组件 -
追踪数据流
参见 references/root-cause-tracing.md 了解向后追踪技术。
快速版本:坏值源自何处?持续向上追踪直到找到源头。在源头修复,而不是症状。
第二阶段:模式分析
- 寻找工作示例 - 在同一代码库中定位类似的正常工作的代码
- 与参考实现比较 - 完整阅读参考实现,不要略读
- 识别差异 - 列出正常和故障之间的每一个差异
- 理解依赖关系 - 有哪些设置、配置、环境假设?
第三阶段:假设与测试
- 形成单一假设 - “我认为X是根本原因,因为Y”
- 最小化测试 - 尽可能最小的更改,一次只改变一个变量
- 验证后再继续 - 有效?进入第四阶段。无效?新假设,不要叠加修复
第四阶段:实施
-
创建失败测试用例 - 最简单的复现,如果可能则自动化
-
实施单一修复 - 一个更改,不要进行"既然我在这里"的改进
-
验证修复 - 测试通过吗?没有回归吗?
-
如果修复无效:
- 计数:你尝试了多少次修复?
- 如果 < 3:返回第一阶段,重新分析
- 如果 >= 3:停止并质疑架构
-
如果3+次修复失败:质疑架构
表明架构问题的模式:
- 每次修复都揭示新的共享状态/耦合
- 修复需要"大规模重构"
- 每次修复在其他地方产生新症状
停止。在尝试更多修复之前与用户讨论。
危险信号 - 停止并遵循流程
如果你发现自己这样想:
- “先快速修复,稍后调查”
- “只是试试改变X看看”
- “添加多个更改,运行测试”
- “我确信是X,让我修复它”
- “再试一次修复”(当已经尝试2次以上)
- 在追踪数据流之前提出解决方案
所有这些都意味着:停止。返回第一阶段。
支持技术
纵深防御
当你修复一个bug时,在每一层都进行验证:
| 层级 | 目的 | 示例 |
|---|---|---|
| 入口点 | 在API边界拒绝无效输入 | if (!dir) throw new Error('dir required') |
| 业务逻辑 | 确保数据对操作有意义 | 处理前验证 |
| 环境防护 | 防止在特定上下文中进行危险操作 | 在测试中拒绝在tmpdir外进行git init |
| 调试插桩 | 捕获上下文用于取证 | 在危险操作前记录堆栈跟踪 |
单一验证感觉足够,但不同的代码路径会绕过它。使bug在结构上不可能发生。
基于条件的等待
不稳定的测试猜测时间。改为等待实际条件:
# 不好:猜测时间
await asyncio.sleep(0.05)
result = get_result()
# 好:等待条件
await wait_for(lambda: get_result() is not None)
result = get_result()
模式:
async def wait_for(condition, timeout_ms=5000):
start = time.time()
while True:
if condition():
return
if (time.time() - start) * 1000 > timeout_ms:
raise TimeoutError("条件未满足")
await asyncio.sleep(0.01) # 每10ms轮询一次
常见合理化借口
| 借口 | 现实 |
|---|---|
| “问题很简单,不需要流程” | 简单问题也有根本原因。对于简单bug,流程很快。 |
| “紧急情况,没时间走流程” | 系统化调试比猜测和检查的混乱方式更快。 |
| “先试试这个,然后调查” | 第一次修复设定了模式。从一开始就做对。 |
| “我看到问题了,让我修复它” | 看到症状 != 理解根本原因。 |
| “再试一次修复”(2次失败后) | 3次以上失败 = 架构问题。质疑模式,不要再修复。 |
验证
运行:python scripts/verify.py
参考资料
- references/root-cause-tracing.md - 通过调用堆栈向后追踪bug