调试大师:系统化 Debug 方法论
掌握系统化的调试方法和工具,快速定位和修复 Bug
- 掌握系统化调试流程
- 学会使用 pdb 调试器
- 掌握二分法和最小复现
- 了解常见 Bug 模式和排查思路
调试不是瞎猜
新手调试靠随便改改试试,高手调试靠系统化方法。调试的核心是:观察现象、提出假设、验证假设、定位根因、修复验证。
不要在没理解问题之前改代码。你要是说不出为啥改动能修好问题,那就是瞎碰运气。先复现,再定位,最后修复。
系统化调试流程
稳定复现:找到能100%复现Bug的最小步骤。不能复现的Bug没法调试。
收集信息:读完整错误堆栈、加日志、查输入数据、看最近的改动。
二分定位:注释掉一半代码、用 git bisect 找引入 Bug 的提交、在关键位置打印中间值。
提假设:查问题时先猜最可能的原因,别同时怀疑所有地方。
验证假设:在调试器里设断点查变量值,或者写个最小测试用例验证。
修复并验证:修复完要确认Bug没了,还要跑测试,别搞出新问题。
pdb 调试器实战
# 在代码中插入断点
import pdb
def calculate_total(items, discount_rate):
total = 0
for item in items:
pdb.set_trace() # 程序在这里暂停,进入交互式调试
total += item["price"] * item["quantity"]
total *= (1 - discount_rate)
return total
# pdb 常用命令:
# n (next) 执行下一行,不进入函数
# s (step) 执行下一行,进入函数
# c (continue) 继续执行到下一个断点
# p expr 打印表达式的值
# pp expr 美化打印
# l (list) 显示当前代码
# w (where) 显示调用栈
# u (up) 上移栈帧
# d (down) 下移栈帧
# b file:line 设置断点
# cl 清除断点
# a (args) 打印当前函数参数
# !statement 执行任意 Python 语句
# q (quit) 退出
# Python 3.7+ 更简单的断点
breakpoint() # 等价于 pdb.set_trace()print调试就按这几点来:打印变量要带变量名和值,比如print(f"x={x}")想看类型信息用repr(),比如print(repr(x))调试完立即删掉这些print,或者改成logger.debug
二分法调试
代码二分:注释掉一半代码,看 Bug 是否还在Git 二分:找到引入 Bug 的提交 git bisect start git bisect bad HEAD git bisect good v1.2.0 # Git 自动 checkout 中间版本,你测试后标记 good/bad git bisect reset 数据二分:如果处理 1000 条数据时出错 先试前 500 条,再试 250 条,缩小到具体数据
常见 Bug 模式速查
调试心态
Bug 不是你的敌人,它是系统在告诉你信息。保持冷静。
盯着代码超过30分钟没进展,就离开歇会儿,去散步、喝水。大脑会在后台继续处理。
跟同事说问题(橡皮鸭调试法),说到一半自己就找到答案了。
修复后要问自己:这个 Bug 为什么没被测试发现?我需要加什么测试防止它再次发生?
数据库连接偶尔超时,把超时时间从5秒改成30秒只是在掩盖问题。真正的修复是找到为什么连接慢——是连接池配置的问题?还是网络?还是数据库负载?不这么做的话,问题会以更严重的形式回来。
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
常见根因:内存泄漏导致 OOM 被系统杀掉、连接/文件句柄没关闭耗尽、事件循环里混入同步阻塞、未捕获异常让进程退出。12-Factor App 的建议很实用:配置环境化、日志当成事件流输出、进程保持无状态(状态放数据库/缓存),这样才能随时加副本做水平扩展。
挑战任务
使用 pdb 调试
写一个有 Bug 的函数,用 pdb 一步步调试找到问题并修复。
课后作业
调试实战
故意写一个包含 3 个不同类型 Bug 的程序(逻辑错误+类型错误+边界错误),然后用 pdb 逐一调试定位,记录调试过程。