外观
程序调试方法论
约 1559 字
程序调试方法论
新手和高手调试的差距,不在工具熟练度,在方法论。 新手靠猜(「是不是缓存问题?」),高手靠缩小范围。 这篇讲一套通用的调试流程,前后端通用。
一、调试的本质:排除法
一个 bug 的可能原因有 20 个,调试就是每次排除一批, 而不是逐个试。科学的调试循环:
观察现象 → 提出假设 → 设计实验 → 验证/推翻 → 缩小范围 → 定位关键纪律:一次只改一个变量。同时改三处「说不定好了」, 好了也不知道是哪处起的作用,坏了我更乱。
二、第一守则:读错误信息
90% 的 bug,错误信息里写着答案。但人习惯性跳过:
TypeError: Cannot read properties of undefined (reading 'map')
at UserList.vue:42:15这行告诉你:在 UserList.vue 第 42 行,对一个 undefined 调了 .map。 不是「组件坏了」,是「那个数组还没到」。去第 42 行看: 八成是接口数据没返回就渲染了。
读栈的三步:
- 先看错误类型(Type/Reference/Syntax)—— 哪类问题;
- 看第一条栈帧(你自己的文件)—— 在哪出的事;
- 看错误信息的具体值——
undefined?NaN?具体数字对不上?
英语差的,把错误信息整段丢进搜索引擎 —— 你的错误,一万个人先踩过。
三、缩小范围的四个武器
1. 二分注释法
一段代码不工作?注释掉一半,跑。还出错?bug 在剩下的一半; 不出错了?在你刚注释的里。再对剩下的一半二分。
// 原本:A → B → C → D 四步,结果错了
// 第一次:只跑 A → B,后面注释 结果对 → bug 在 C/D
// 第二次:A → B → C,D 注释 结果错 → bug 在 C比通读四遍代码快得多。
2. 替换法(控制变量)
「是这个库的问题还是我用错了?」—— 用最小复现替换:
- 复杂组件跑不通 → 把数据换成写死的三条,还通不通?
- 接口返回奇怪 → 用 Postman/curl 直接打,绕过前端;
- 框架行为诡异 → 用原生 API 写等价十行代码,对不对?
最小复现是 bug 报告的金标准,也是定位的神器。 本站排「Nginx 502」时就是用 curl 直连后端,确认后端没问题, 瞬间把范围缩到 Nginx 层。
3. 打印大法(Printf Debugging)
别瞧不起 console.log,它是最快的探针:
console.log('进函数了')
console.log('参数:', JSON.stringify(params))
console.log('接口返回:', data)
console.log('这里执行了 3 次') // 怀疑多调/少调时计数技巧:打印边界值。循环打印首尾元素、接口打印状态码和长度、 怀疑类型问题打印 typeof x。
高级一点的:条件打印(只在你关心的输入下打), 否则日志刷屏看不见重点。
4. 断点调试
console.log 不够用了上断点(DevTools/IDE):
- 想看「这个变量什么时候变成 undefined」→ 条件断点;
- 想一步步看「先执行了谁」→ 单步 + 调用栈;
- 想看「这个 DOM 被谁改了」→ DOM breakpoint。
详细操作在 Chrome DevTools(在新窗口打开) 和 VS Code 技巧(在新窗口打开)。
四、按症状分类的套路
「昨天还好好的,今天坏了」
想:最近改了什么? git log / git diff 看最近的提交。 八成是某个改动引入的。直接 git stash 或切到昨天的 commit 验证。
「我电脑上好的,别人那不行」
差异在三处:环境(Node 版本、依赖版本)、数据(他那有你没有的数据)、 缓存(他那有旧缓存)。逐项对齐:Node 版本、lock 文件、 清缓存、用他那的账号复现。
「偶发,跑十次错一次」
并发/时序问题。思路:
- 加日志记录每次的输入输出,错的那次就是样本;
- 想「哪两个操作可能撞上」:两个请求同时改一条数据? 定时器和用户操作同时触发?
- 复现技巧:连点十次、快速切页面、弱网模拟。
「升级了某个东西就坏了」
回退到旧版本确认 → 看 changelog/migration guide → 八成是 breaking change(主版本号变了就是这个信号, 语义化版本(在新窗口打开))。
五、debug 的仪式感
高手的习惯:
- 先复现。不能稳定复现的 bug 最难搞 —— 先找到「必现的步骤」,这是起点;
- 记录时间线。什么时候发现的、当时在干嘛、 报什么错。写下来,别靠脑子;
- 一次一个假设。写下来假设再验证,别脑子一热瞎试;
- 修完想三秒。这个 bug 为什么会发生? 同类问题还有没有?要不要加测试防回归?
六、什么时候该求助
卡 30 分钟就该问人了(继续钻是沉没成本)。 好的求助姿势(以本站 Issues 为例):
标题:RSS 插件生成的 feed.xml 缺少日期
环境:VuePress 2.0.0-rc.31、Plume 1.0.0-rc.214、Node 22
复现步骤:
1. npm run docs:build
2. 打开 dist/feed.xml
3. 看到 <pubDate> 为空
已尝试:
- 检查了 createTime frontmatter(有值)
- 单独跑插件逻辑正常
预期:每个 item 都有 pubDate
实际:为空「环境 + 复现步骤 + 已尝试 + 预期 vs 实际」四件套。 这样的人问问题,帮的人爽快,你也快。怎么把问题问好, 也是一门功课。
七、心态:bug 不是你的错
最后说句重要的:写出 bug 是常态,不是能力问题。 每个程序员每天都在和 bug 打交道,包括那些你仰望的大佬。 调试是工作的一部分,不是「我居然会犯错」的耻辱柱。
保持耐心,用流程代替情绪。Bug 修完的那一刻, 你就是这个系统最懂的人 —— 这感觉,还挺爽的。
小结
调试三板斧:读错误信息(答案常在里面)、 缩小范围(二分/替换/打印/断点)、按症状套套路。 配上「先复现、一次一假设、修完想三秒」的仪式感, 九成 bug 都能变成「十分钟小作业」。剩下的那一成, 交给求助的艺术。