图 07 · 调试
如何从"有什么东西坏了"走到有据可查的修复?
证据漏斗:从模糊症状开始,逐步收窄到最小可复现案例,建立假设,通过仪表化验证,最后产出一个防止回归的测试。每一级都排除一类可能的原因。
为什么先建立反馈循环?(阶段 2 的重要性)
在可复现失败存在之前进行的所有分析都是猜测。一旦有了失败测试,你就有了一个可以对抗的目标——每次修改后立刻知道是否更接近修复。没有反馈循环,调试是一场猜谜游戏。
阶段 7 不是可选的
修复后如果不写回归测试,这个 bug 会在三个月后由不同的人用完全相同的方式再次引入。回归测试将「发现过这个 bug」的知识编码进代码库,比任何文档都更可靠。
实例:实体随机消失(竞态条件)
阶段 2 → 5 的过程
阶段 2:写一个并发测试,在 100 次 activate() 后检查实体计数。立刻失败(约 3%)。
阶段 3:移除网络层、UI 层,只保留 EntityRegistry 的两个并发操作。仍然失败。
阶段 4:假设是 Set 的 delete() 和 add() 在同一 microtask 内无锁执行。
阶段 5:添加 console.time,确认两个操作在同一 tick 内。
阶段 6 → 7 的产物
阶段 6:最小修复:在 activate() 中加入操作队列(单消费者模式),解决竞态。
阶段 7:将阶段 2 的 100 次并发测试重命名为 regression: entity count stable under concurrency,加入 CI。
线性阅读顺序:症状(模糊报告)→ 建立可复现失败(先有失败测试,再分析)→ 最小化案例(排除干扰因素)→ 建立最简假设 → 仪表化验证(如不符 → 退回阶段 4)→ 最小修复 → 回归测试(永久守卫,合并到 CI)→ 产出:fix + 防回归测试。