第一章 调试入门:从“找茬”到“治病”
调试这件事,说白了就是给代码看病。我做了十几年软件,见过太多人把调试当成“碰运气”——改一行代码跑一下,不行再改回来。嗯,这其实不是调试,是瞎蒙。
今天咱们聊聊调试的本质,它跟测试到底有啥区别,以及最常用的几把“手术刀”怎么用。
1.1 调试的本质:定位与修复
调试是什么?我的理解很简单:找到代码里“想的不对”和“写的不对”之间的差距。
你想想看,写代码时脑子里有个预期——这个函数应该返回42。结果跑出来是0。调试就是去追查:为什么是0?哪一步出了问题?
我个人习惯把调试分成三步:
- 复现:让bug稳定出现。不能复现的bug,基本没法修。
- 定位:缩小范围,找到罪魁祸首是哪一行、哪个变量。
- 修复:改代码,并确认没引入新问题。
核心观点:调试不是“修代码”,而是“理解代码”。你理解了,修复自然就来了。
我在项目中遇到过最典型的例子:一个支付模块偶尔多扣用户一分钱。测试跑了100次都没问题,但线上就是有用户投诉。后来我花了两天时间,在日志里加了一堆标记,最终发现是浮点数精度问题——0.01 + 0.02 不等于 0.03,这个坑大家应该都踩过。
1.2 调试与测试的区别
很多人分不清调试和测试,觉得都是“找bug”。其实差别挺大的。
| 维度 | 测试 | 调试 |
|---|---|---|
| 目标 | 发现bug是否存在 | 找到bug的原因并修复 |
| 执行者 | 测试人员或自动化脚本 | 开发人员 |
| 工具 | 单元测试框架、CI系统 | 调试器、日志、分析工具 |
| 输出 | 测试报告(通过/失败) | 代码修改(修复补丁) |
| 时机 | 开发中、发布前 | 测试失败后、线上出问题时 |
举个例子:测试告诉你“登录按钮点了没反应”。这是测试的结论。调试要回答的是“为什么没反应?是事件没绑定?还是接口返回了500?还是前端没处理响应?”
我曾经接手过一个项目,测试报告写了200页,全是“功能A失败”、“功能B异常”。但开发组看了三个月,一个bug都没修好。为什么?因为测试只告诉了你“哪里疼”,没告诉你“为什么疼”。调试才是那个做诊断的医生。
我的建议:写测试时,顺便想想“如果这个测试失败了,我该怎么调试?”提前埋好日志、加好断言,能省很多事。
1.3 常见调试工具概览
工具不在多,够用就行。我平时主要用这三样:print、pdb、IDE调试器。它们各有各的适用场景。
1.3.1 print:最原始,也最直接
别看不起print。我到现在写新代码,第一反应还是加print。它简单、零学习成本、在任何环境都能跑。
def calculate_price(amount, discount):
print(f"amount={amount}, discount={discount}") # 看输入
result = amount * (1 - discount)
print(f"result={result}") # 看输出
return result
但print有个大问题:容易污染代码。调试完了忘了删,或者删不干净,上线后控制台刷一堆垃圾信息。我见过有人线上代码里留着几百行print,把磁盘日志都打爆了。
避坑指南:我曾经在生产环境忘记删一个print,结果打印了用户手机号,差点被合规部门约谈。所以我现在都用日志库(logging)代替print,可以控制级别,上线自动关闭debug输出。
1.3.2 pdb:Python自带的调试器
当print不够用的时候,就该上pdb了。它可以让你在代码任意位置暂停,查看变量、单步执行。
用法很简单:
import pdb
def divide(a, b):
pdb.set_trace() # 程序会停在这里
return a / b
divide(10, 0)
运行后你会进入交互模式,常用命令:
n:执行下一行c:继续执行到下一个断点p 变量名:打印变量值l:查看当前上下文代码q:退出调试
我个人习惯在复杂循环里用pdb。比如处理一个100万行的文件,第99999行报错了。用print得改代码重新跑,用pdb可以直接在报错处停下来,看当时的变量状态。
1.3.3 IDE调试器:图形化的“透视眼”
如果你在用VS Code、PyCharm这类IDE,调试器是最高效的工具。它把pdb的功能做成了可视化界面。
核心操作就几个:
- 断点:在行号旁边点一下,程序跑到这里就停。
- 单步跳过:一行一行执行,不进入函数内部。
- 单步进入:进入函数内部,看它怎么算的。
- 变量面板:所有变量值一目了然,不用自己print。
- 调用栈:看当前是哪个函数调了哪个函数,一路回溯。
你想想看,一个函数嵌套了5层,用print你得在每个函数里加一行,跑完还得删。用IDE调试器,直接在入口打断点,然后“单步进入”一层层往下看,效率高太多了。
小技巧:条件断点非常实用。比如循环1000次,只在第500次时停下来。右键断点,设置条件为 i == 500 就行。我曾经用这个技巧,十分钟找到了一个只在特定数据量下才触发的bug。
1.4 三种工具怎么选?
我一般这样选:
- 简单逻辑、快速验证:用print,几秒钟搞定。
- 复杂流程、需要交互:用pdb,轻量且强大。
- 大型项目、多层调用:用IDE调试器,可视化效率最高。
没有哪个工具是万能的。我见过有人只用print,遇到多线程bug就抓瞎。也见过有人只会用IDE调试器,上了服务器没有图形界面就完全不会了。所以我的建议是:三个都学会,根据场景切换。
一句话总结:调试的本质是理解代码的运行过程。工具只是帮你“看到”这个过程的手段。别纠结用哪个,先动手调起来再说。
下一章咱们聊聊日志系统——怎么打日志才能既帮自己调试,又不影响线上性能。嗯,这个坑我也踩过不少,到时候细说。