在30分钟内找出所有错误的调试框架
Source: Dev.to
框架:REDUCE
R复制 → E检查 → D划分 → U理解 → C改变 → E评估
步骤 1:复现(最多 5 分钟)
如果你无法复现它,就无法修复它。就这么简单。
目标: 找到触发 bug 的 最小步骤集合。
不是 “用户在做某事,然后就崩溃了”。
1. Create a new user with email "test@test.com"
2. Add item to cart
3. Click checkout
4. Enter the same email "test@test.com" in billing
5. BUG: "Account already exists" error on payment page
如果无法在本地复现
检查环境差异。90% 的“无法复现” bug 由以下原因导致:
- 不同的数据(生产环境有本地没有的边缘案例)
- 不同的时机(需要并发用户的竞争条件)
- 不同的配置(环境变量、功能开关、软件包版本)
- 缓存(本地缓存为空,生产缓存有过期数据)
给此步骤设定时间盒。 如果在 5 分钟内无法复现,请在进一步尝试之前收集更多数据(日志、截图、用户会话)。
步骤 2:检查(3 分钟)
在动代码之前,先收集证据。你知道(而不是怀疑)什么?
FACTS:
- Error: "Account already exists" on POST /api/checkout
- Happens when billing email matches an existing user
- Started after deploy v2.3.1 (didn’t happen in v2.3.0)
- Error comes from UserService.findOrCreate()
- Only happens with email‑based lookup, not with userId
UNKNOWNS:
- Why does checkout call findOrCreate? (It shouldn't create users)
- What changed between v2.3.0 and v2.3.1?
- Is this related to the auth refactor merged last week?
把这些写下来——在纸上、笔记里、注释中——任何实体的地方。这样可以防止你在原地打转。
第3步:划分(关键步骤)
这是决定成败的一步。 与其阅读所有代码,不如对 bug 进行二分搜索。
请求流是:Router → Middleware → Controller → Service → Repository → Database。
问题: bug 在前半段还是后半段?
// Add a log at the midpoint: the service layer
async checkout(data: CheckoutData) {
console.log('SERVICE INPUT:', JSON.stringify(data));
const user = await this.userService.findOrCreate(data.email);
console.log('SERVICE USER:', JSON.stringify(user));
// ... rest of checkout
}
运行复现步骤并检查日志。
- 如果
SERVICE INPUT的数据错误 → bug 在前半段(router/middleware/controller)。 - 如果
SERVICE INPUT正确但SERVICE USER错误 → bug 在findOrCreate。 - 如果两者都正确 → bug 在后半段。
现在你已经把搜索空间减半。重复此过程。每次划分只需 1‑2 分钟。经过 4‑5 次划分,你就能把范围从整个代码库缩小到单个函数。
这就是
git bisect,但针对代码路径而不是提交。
第4步:理解(不要只修复表面症状)
你已经找到了有 bug 的函数。在修改它之前,先弄清楚 它为什么会错。
// The bug:
async findOrCreate(email: string): Promise {
const user = await this.db.users.findOne({ email });
if (!user) {
return this.db.users.create({ email }); // BUG is HERE
}
return user;
}
该函数的作用是 查找已有用户或创建新用户。结账流程会调用它来查找用户。如果账单邮箱与账户邮箱不同,函数会尝试使用账单邮箱创建新用户——这会与已有账户产生冲突。
根本原因: 结账时调用了 findOrCreate,而应该调用 findByEmail。findOrCreate 的行为在注册时是正确的,但在结账时就是错误的。
如果你仅仅对症下药(例如在 create 周围加一个 try‑catch),只会掩盖架构上的错误,后续会产生更多 bug。
第5步:更改(修复)
修复代码以解决根本原因,而不是症状。
// Fix: checkout should find, not create
async checkout(data: CheckoutData) {
const user = await this.userService.findByEmail(data.billingEmail);
if (!user) {
throw new BadRequestError('No account found with this email');
}
// ... proceed with checkout
}
第 6 步:评估(验证)
- 运行复现步骤 → bug 已消失 ✅
- 运行测试套件 → 没有其他问题 ✅
- 为此特定 bug 编写测试
test('checkout with non‑matching billing email returns 400', async () => {
const user = await createUser({ email: 'account@test.com' });
const res = await api.post('/checkout', {
...validCheckoutData,
billingEmail: 'different@test.com',
userId: user.id,
});
expect(res.status).toBe(400);
// Verify no new user was created
const users = await db.users.findAll({ email: 'different@test.com' });
expect(users).toHaveLength(0);
});
此测试可防止该 bug 再次出现。
高级技巧
技巧:Printf 调试器(这并不丢人)
console.log 调试常被诟病,但只要系统化使用,它是最实用的工具。
不要这样做:
console.log('here');
console.log('here2');
console.log(thing);
应该这样做:
console.log('[CHECKOUT] Input:', {
email: data.email,
itemsCount: data.items.length,
});
添加清晰、带上下文的前缀,只记录你需要的数据。这样可以保持输出可读,便于快速发现异常。
调试清单与技巧
1️⃣ 删除调试日志
console.log('[CHECKOUT] User lookup result:', { found: !!user, userId: user?.id });
console.log('[CHECKOUT] Payment attempt:', { amount, currency, method });
操作: 完成后,运行以下命令将代码库中所有这些日志删除:
grep -r "console.log.*\[CHECKOUT\]" -l | xargs sed -i '/console\.log.*\[CHECKOUT\]/d'
2️⃣ 技巧:橡皮鸭解释
目标: 用普通英文解释 bug。
“结账页面在账单邮箱与已有用户匹配时显示‘账户已存在’。结账流程调用
findOrCreate,该函数在不该创建用户时尝试创建用户。这个问题出现在我们把账单邮箱设为结账表单的独立字段之后。”
大多数情况下,仅仅把问题说出来就能触发解决方案。
3️⃣ 技巧:差异调查
# 工作版本与出错版本之间有什么变化?
git diff v2.3.0..v2.3.1 -- src/checkout/
# 谁改动了结账流程?
git log --oneline v2.3.0..v2.3.1 -- src/checkout/
# 那个特定提交做了什么?
git show abc123
提示: bug 是由某一次具体改动引入的。定位到该改动,就找到了根本原因。
4️⃣ 技巧:环境切换
问题: bug 只在生产环境出现?把生产环境带到本地机器上。
# 将生产数据复制到本地(已脱敏!)
pg_dump production_db --exclude-table=secrets | psql local_db
# 在本地回放生产流量
# (工具:GoReplay、mitmproxy)
# 对比配置文件
diff config/production.yml config/local.yml
注意: 与一位了解情况的同事简短交流,往往能省下数小时的猜测时间。
5️⃣ 社区提问
你常用的调试技巧是什么?我发现大多数人在“划分”这一步就直接跳到阅读所有代码。你有系统化的方法吗,还是
console.log战士?
欢迎留言讨论!
6️⃣ 额外资源
💡 AI 编码提示包 – 超过 50 条实战提示、6 个 Cursor 规则文件以及 Claude Code 工作流模板。
在 Gumroad 上获取 – $9,终身更新。