Debug 不是玄学:写给小白的调试心法 + 8 个必杀技
为什么有的人改 bug 像在闹钟里挑黄豆,有的人却像庖丁解牛?答案是「调试心法」+「8 个工具技巧」。
一段令人崩溃的对话
小白:我代码不行了。
我 :报什么错?
小白:没报错,就是不对。
我 :哪里不对?
小白:反正不对……
新手 Debug 的最大问题,不是"技术不够",是心法不对。这篇文章先讲心法,再讲工具。
心法:Debug 是一个"科学方法"
每次 Debug,本质都是做实验。请把下面 5 步刻在脑子里:
1) 现象:到底什么不对? → 描述清楚!
2) 假设:我猜是什么原因?
3) 验证:怎么用最小代价验证我的猜测?
4) 结论:猜对 / 猜错?
5) 修复 + 复盘:写下"以后怎么避免"
如果你的 Debug 没有"假设"和"验证",那叫乱试,不叫调试。
心法 1:先把现象描述清楚
"代码不对" → 不是描述。
合格的描述包含 5 件事:
- 我做了什么(输入、操作)
- 我期望发生什么
- 实际发生了什么
- 是否100% 复现?什么条件下出现?
- 错误信息 / 截图 / 日志
练习:把以下"小白式"描述改成"程序员式"描述:
❌ "登录登不上去。" ✅ "在 Chrome 124,输入用户名
tom、密码123,点登录按钮无反应;F12 看到 Network 中/api/login请求返回 500,响应 body 是Cannot read property 'id' of undefined。"
后者,问题已经解决了一半。
心法 2:缩小问题范围(二分法)
任何复杂 bug 都可以通过"砍一半"的方式定位:
全部代码 ────► 哪个文件出问题? ────► 哪个函数? ────► 哪一行?
Git 用户可以用 git bisect:
git bisect start
git bisect bad # 当前是坏的
git bisect good v1.2.0 # 那个版本是好的
# Git 会自动切到中间的某个 commit,让你测试
git bisect good # 这个版本是好的
git bisect bad # 这个版本是坏的
# ……反复几次,Git 会精确告诉你是哪个 commit 引入的问题
git bisect reset
10 万行代码 + 1000 个 commit,最多 10 次就能找到那个"罪魁祸首"。
心法 3:相信编译器和报错
新手的本能:看到报错就慌、关掉、不读。 老手的本能:把报错复制下来,逐字读。
90% 的报错本身已经在告诉你答案。比如:
TypeError: Cannot read properties of undefined (reading 'name')
at getUserName (user.js:12:18)
这一段告诉了你:
- 错误类型:TypeError
- 在 user.js 第 12 行,第 18 列
- 你试图读
undefined.name
结论:去 user.js:12 看一下,那个对象为什么是 undefined。
心法 4:不要"加点 console.log 看看"
console.log 不是不好,但是要有目的地打印:
// ❌ 漫无目的
console.log(user);
console.log("hello");
console.log(123);
// ✅ 验证一个假设
console.log("✅ assumption: user has id at this point", { user });
打印之前先想:"如果它是 X 我就知道 bug 在 Y"。这才叫 Debug。
8 个必杀技工具
1. 浏览器断点(Source 面板)
打开 F12 → Source → 找到你的 JS 文件 → 点行号设断点。
代码运行到这里会暂停,你可以:
- 看所有变量的值
- 一步一步往下走(F10 / F11)
- 修改变量值再继续(神器)
2. debugger 语句
function add(a, b) {
debugger; // 程序运行到这里 + 开发者工具打开 → 自动停下
return a + b;
}
非常适合不想找文件的临时调试。记得提交前删掉!
3. 条件断点
右键代码行号 → Add conditional breakpoint:
i === 999 // 只有当 i 是 999 时才停下
适合循环里某次出错的场景。
4. Network 面板
请求挂了?先看 Network:
- 状态码(4xx / 5xx?)
- Request Payload(你发的对吗?)
- Response(后端返了啥?)
- Timing(哪一步慢?)
5. console.table 与 console.group
const users = [{name:"a",age:1}, {name:"b",age:2}];
console.table(users); // 表格形式打印
console.group("处理过程");
console.log("step 1");
console.log("step 2");
console.groupEnd();
6. JSON.stringify 看深层对象
console.log(user); // 可能折叠看不清
console.log(JSON.stringify(user, null, 2)); // 缩进展开
7. 二分注释法
代码量大、不知道哪行出错时:注释一半运行 → 没事?那 bug 在另一半。继续二分,5 次内定位。
8. 写一个最小复现
"在我的代码里它出错了" 是最难复现的状态。
"我做了一个 20 行的最小例子也出错" 是最容易解决的状态。
写最小复现的过程中,80% 的 bug 你自己就解决了。剩下 20% 拿这个最小例子去问别人,对方也能秒回。
Bug 的 5 大常见类型
| 类型 | 表现 | 排查方向 |
|---|---|---|
| 拼写错误 | 报 undefined、is not a function |
看变量名、属性名 |
| 类型错误 | "1" + 1 = "11",比较失败 | 加 typeof 打印 |
| 异步竞态 | 偶尔成功偶尔失败 | 看 await / Promise 有没漏 |
| 副作用未清理 | 内存泄漏、重复请求 | 看 useEffect 的清理函数 |
| 边界值 | 空数组、最后一个元素、闰年 | 测 0、1、N、N+1 |
一份 Debug 清单(贴在墙上)
每次卡住超过 10 分钟,照着问自己一遍:
□ 我能用一句话讲清楚 bug 现象吗?
□ 我读了完整的报错信息了吗?
□ 我猜的原因是什么?我用什么方式验证它?
□ 我把问题缩到最小范围了吗?
□ 我重启 / 清缓存试过了吗?
□ 我对照过 git log 了吗?哪次 commit 之后变坏的?
□ 我写最小复现了吗?
□ 我去 GitHub Issue / Stack Overflow 搜过完整报错了吗?
最后
写代码的时间 30%,Debug 的时间 70%。所以:
会 Debug 的人不是天才,而是比别人更系统。
「图难于其易,为大于其细。」 越复杂的 bug,越要从最简单的假设、最小的实验开始。把心法用熟,工具不过是一些按钮而已。