系统化调试
核心原则
在进行任何修复前,首先进行根本原因调查。
永远不要应用只是掩盖问题的症状补丁。在尝试修复之前,先了解为什么会失败。
四阶段框架
第1阶段:根本原因调查
在接触任何代码之前:
- •彻底阅读错误消息 - 每个词都很重要
- •一致地重现问题 - 如果无法重现,就无法验证修复
- •检查最近的更改 - 在此开始失败之前改变了什么?
- •收集诊断证据 - 日志、堆栈跟踪、状态转储
- •追踪数据流 - 遵循调用链以找到坏值的来源
根本原因追踪技术:
code
1. 观察症状 - 错误在哪里显示? 2. 找到直接原因 - 哪个代码直接产生错误? 3. 询问"谁调用了这个?" - 向上映射调用链 4. 继续向上追踪 - 在堆栈中向后遵循无效数据 5. 找到原始触发器 - 问题实际上在哪里开始?
关键原则: 永远不要仅在错误出现的地方修复问题——总是追踪到原始触发器。
第2阶段:模式分析
- •定位工作示例 - 找到类似的代码可以正确工作
- •完全比较实现 - 不只是浏览
- •识别差异 - 工作代码和破坏代码之间有什么不同?
- •了解依赖关系 - 这个代码依赖什么?
第3阶段:假设和测试
应用科学方法:
- •表述一个清晰的假设 - "错误发生是因为X"
- •设计最小测试 - 一次改变一个变量
- •预测结果 - 如果假设正确会发生什么?
- •运行测试 - 执行并观察
- •验证结果 - 它的行为是否与预测一致?
- •迭代或继续 - 如果错误则改进假设,如果正确则实施
第4阶段:实施
- •创建失败的测试用例 - 捕获bug行为
- •实施单一修复 - 解决根本原因,而不是症状
- •验证测试通过 - 确认修复有效
- •运行完整测试套件 - 确保没有回归
- •如果修复失败,停止 - 重新评估假设
关键规则: 如果连续三次或更多修复失败,停止。这表明存在需要讨论的架构问题,而不是更多补丁。
红旗 - 流程违反
如果你发现自己在想,立即停止:
- •"先快速修复,之后再调查"
- •"再尝试一次修复"(在多次失败之后)
- •"这应该行得通"(没有理解原因)
- •"让我试试..."(没有假设)
- •"在我的机器上可以工作"(没有调查差异)
更深层问题的警告信号
连续修复在不同区域揭示新问题表明存在架构问题:
- •停止打补丁
- •记录您发现的内容
- •在继续前与团队讨论
- •考虑设计是否需要重新思考
常见调试场景
测试失败
code
1. 阅读完整的错误消息和堆栈跟踪 2. 识别哪个断言失败和原因 3. 检查测试设置 - 测试环境正确吗? 4. 检查测试数据 - 模拟/fixture正确吗? 5. 追踪到意外值的来源
运行时错误
code
1. 捕获完整的堆栈跟踪 2. 识别抛出的行 3. 检查什么值是undefined/null 4. 向后追踪以找到坏值的来源 5. 在源头添加验证
"它之前能工作"
code
1. 使用git bisect找到破坏提交 2. 将更改与先前的工作版本进行比较 3. 识别什么假设改变了 4. 在假设违反的来源处修复
间歇性失败
code
1. 查找竞态条件 2. 检查共享可变状态 3. 检查异步操作顺序 4. 查找时序依赖 5. 添加确定性等待或正确的同步
调试检查清单
在声称bug已修复前:
- • 根本原因已识别并记录
- • 假设已形成并测试
- • 修复解决根本原因,而不是症状
- • 创建了重现bug的失败测试
- • 现在测试通过修复
- • 完整测试套件通过
- • 未使用"快速修复"合理化
- • 修复是最小化和集中的
成功指标
系统化调试实现约95%的首次修复率,而临时方法约40%。
您做对的迹象:
- •修复不会产生新bug
- •您可以解释为什么会发生bug
- •类似的bug不会再次出现
- •修复后代码更好,而不仅仅是"可以工作"
与其他技能的集成
- •testing-patterns: 在修复前创建重现bug的测试