第二章:print调试法——最朴素的调试利器

说实话,很多新手觉得print调试太low了。他们一上来就想着用IDE断点、用各种高端工具。但我得说句公道话——print调试法,是每个程序员都应该掌握的看家本领。

我在项目中遇到过好几次这样的情况:生产环境出了问题,断点打不上去,日志级别又不对。这时候怎么办?靠的就是print。说白了,它就像医生的听诊器,简单但管用。

2.1 print语句的妙用

很多人觉得print就是往控制台扔几个变量值。嗯,这没错,但太粗糙了。我个人的习惯是,把print当成一种「轻量级埋点」。

举个例子,你怀疑某个循环有问题:

for i in range(1000):
    result = process(i)
    # 这里加个进度提示
    if i % 100 == 0:
        print(f"[进度] 已处理 {i}/1000 条")
    # 关键路径上的数据
    if result is None:
        print(f"[异常] 第 {i} 条数据返回空")

你看,这样打印出来的信息,一眼就能看出问题在哪。而不是满屏的数字乱飞。

小技巧: 用不同的前缀标记不同类型的输出。比如 [INFO]、[WARN]、[ERROR]、[DEBUG]。这样过滤起来特别方便。

2.2 格式化输出调试信息

光会print还不够,你得会「优雅地print」。我见过太多人这样写:

print(a)
print(b)
print(c)

然后控制台里一堆数字,根本分不清谁是谁。你想想看,这跟没调试有什么区别?

我建议用f-string或者format来格式化输出:

# 推荐的做法
user_id = 1024
user_name = "张三"
balance = 99.99

print(f"[用户信息] id={user_id}, name={user_name}, balance={balance:.2f}")

# 或者用占位符
print("[调试] 用户 %s (ID: %d) 余额: %.2f" % (user_name, user_id, balance))

为什么要格式化?因为可读性就是生产力。你调试的时候,一眼就能看出每个字段的值,而不是对着屏幕数数字。

输出方式 可读性 适用场景
裸print 差 临时看一眼
f-string 优 日常调试首选
format 良 需要对齐时
JSON序列化 优 复杂数据结构

核心原则: 调试输出要让人「一眼看懂」,而不是「仔细分析」。

2.3 条件打印与调试开关

这是print调试法的进阶技巧。你不可能把所有print都留在代码里,那会变成噪音。我个人的做法是——加一个调试开关。

# 调试开关
DEBUG = True  # 上线前改成 False

def debug_print(msg):
    if DEBUG:
        print(f"[DEBUG] {msg}")

# 使用
debug_print(f"用户登录: {user_id}")
debug_print(f"数据库查询耗时: {elapsed}ms")

这样,上线的时候只需要改一个变量,所有调试输出就都消失了。干净利落。

更高级一点,你可以用环境变量来控制:

import os

DEBUG = os.environ.get("APP_DEBUG", "0") == "1"

def debug_print(msg, level="INFO"):
    if not DEBUG:
        return
    print(f"[{level}] {msg}")

# 使用
debug_print("开始处理请求", "INFO")
debug_print(f"请求参数: {params}", "TRACE")
注意: 我曾经在生产环境吃过亏——忘了关调试开关,结果日志文件一天涨了10个G。所以一定要养成习惯,上线前检查DEBUG标志。

2.4 条件打印的妙用

有时候你不想打印所有信息,只想看特定条件下的数据。这时候条件打印就派上用场了。

# 只在错误发生时打印
for item in data_list:
    result = process(item)
    if result is None:
        print(f"[ERROR] 处理失败: item={item}, 上下文={context}")

# 只在特定阈值触发时打印
for i in range(10000):
    if i % 1000 == 0:
        print(f"[进度] 已完成 {i}/10000")

你想想看,如果每一条都打印,控制台早就刷屏了。但加上条件,你就能精准地抓到关键信息。

2.5 实战中的print调试套路

我总结了一套自己的print调试流程,分享给你:

  1. 定位问题区域:先确定大概哪个函数或循环出了问题
  2. 加标记性print:在入口和出口各加一个,确认执行路径
  3. 加关键变量print:把怀疑的变量值打出来
  4. 加条件print:只在异常条件下打印,减少噪音
  5. 分析并修复:根据输出定位问题
  6. 清理print:修复后记得删掉调试代码
我的习惯: 调试完成后,我会用git diff检查一下,确保没有遗留的print语句。这个习惯帮我避免了好几次线上事故。

2.6 print调试的局限性

当然,print不是万能的。我得实话实说:

  • 多线程环境下,print输出会交错,很难看
  • 性能敏感的场景,print会拖慢速度
  • 无法动态修改调试点,得改代码重新跑
  • 不适合调试GUI程序或嵌入式系统

但话说回来,对于80%的日常调试场景,print完全够用。别小看它,用好了就是一把利器。

嗯,这一章就到这里。记住,工具没有高低之分,只有会不会用。print调试法,值得你花时间练好。