跳到内容

元数据卡

  • 前置知识:调试器基础 + 进阶
  • 预计时间:30 分钟
  • 阅读模式:动手操作
  • 完成标志:完成三个热身和一个递归挑战

你的进度

你学会了断点、条件断点、调用栈、WATCH 面板。现在是时候动手了——把知识变成肌肉记忆。


常见陷阱(先看,避免掉坑)

故事一:断点打在了注释行

有一次我花了两小时,加断点、单步、看变量——啥都没错,但程序就是不停。最后发现,断点打在了注释行上。

IDE 允许你在注释上设断点——但它永远不可能被执行到。调试器不会提醒你"嘿,这行是注释",它只会尊重你的决定。

教训: 实心红点 = 有效断点。空心或带问号 → 打在无效位置了。

故事二:优化后的代码找不到行号

C/C++ 编译器开 -O2 优化时,可能重排指令、内联函数、删未用变量。你设的断点被编译器"优化没了"——跑过去不会停,因为那行代码在二进制里不存在了。

解决: 开发环境用 -O0 -g,发布环境才用 -O2


热身(5 分钟,必做)

  1. 在 VS Code 中打开任意 Python 文件(新建一个也行)
  2. 在你熟悉的函数内第一行设置断点(点行号左侧)
  3. F5 以调试模式运行
  4. F10(步过) 至少 5 行,观察 VARIABLES 面板的变化
  5. 在 WATCH 面板加一个表达式,比如 len(items) 或你代码里有的变量运算

做完这 5 步,你就能在真实场景里直接上手调试了。


挑战:递归函数调试(30 分钟,选做)

这是带 bug 的阶乘函数。它永远不会返回正确结果:

python
# factorial_debug.py
def factorial(n):
    # 这是有 bug 的版本
    if n == 0:   # 应该是 n == 1 或 n <= 1
        return 1
    return n * factorial(n - 1)

print(factorial(5))

语言: Python 3 如何运行: python factorial_debug.py预期正确输出: 120(5×4×3×2×1) 实际输出(有 bug): 0(因为递归到底时 factorial(0) 触发 n == 0,但循环不会在 n=1 停住)

调试步骤:

  1. return n * factorial(n - 1) 行设断点
  2. 按 F5 运行,观察调用栈——每一次递归调用都往栈上压一层
  3. n 加条件断点 n == 2——只在递归深度到 2 时停住
  4. 在 WATCH 面板输入 n * factorial(n - 1)——你会发现它显示 <error>,因为递归函数还没返回时,表达式无法求值
  5. 通过观察 n 的变化轨迹找出 bug:当 n == 0 时,函数返回 1,但 n == 1 时应该返回 1 而不是继续递归调用

排障场景

场景 1:断点打了,F5 按了,程序直接结束了,没停。

诊断: 可能的原因:

  • (a) 断点打在了注释行、空行等无效位置
  • (b) 运行的是错误的目标文件(不是当前编辑器里的文件)
  • (c) C/C++ 编译优化把代码行抹掉了

解决: 确认红点实心,确认调试配置选对了入口,检查编译器参数。

场景 2:步进(F11)时跳进了标准库代码。

诊断: IDE 默认允许进入你安装的库代码。你一般不需要看 sorted() 的内部实现。

解决: VS Code 里搜 "Just My Code" 设置,开启后只调试你自己的代码。IntelliJ 的 Java 模式默认开启此功能。


验收标准

  • [ ] 能理解断点的作用——在代码执行到指定行时暂停
  • [ ] 会使用条件断点过滤不必要的暂停
  • [ ] 能通过 VARIABLES / WATCH 面板观察变量值
  • [ ] 能通过 CALL STACK 面板追溯调用来源
  • [ ] 懂得 Step Over(步过)和 Step Into(步入)的区别
  • [ ] 能在调试控制台求值和修改变量
  • [ ] 知道日志断点(Logpoint)能替代临时 print() 调试

常见卡点

  1. "步进(F11)和步过(F10)到底选哪个?"

    • 步过:不进入函数内部。如果你不想看 sorted() 的实现,用步过。
    • 步进:跳进函数内部。如果你想看自己写的函数逻辑,用步进。
  2. "调试控制台里改了变量,程序真的会变吗?"

    • 会。你写 n = 100,程序里的 n 真的变 100 了。用来测边界可以,但记得恢复。
  3. "为什么看不到上层变量?"

    • 调试器只显示当前栈帧。想看调用者的变量,在 CALL STACK 面板点击那层帧。
  4. "断点太多,每次都要删吗?"

    • 不需要。可以禁用断点(取消勾选),或一次清除所有断点。

旅人笔记

调试器不是报错的工具,是观察的工具print() 像是推测,"这里应该有问题吧";断点像是目击,"我亲眼看到它干了什么"。调用栈让你看清"谁叫谁"的因果链——很多 bug 不是最后一行的错,而是谁把程序引到那条路上去的。

下一站预告

调试器在手,bug 渐行渐远。但你的项目开始依赖越来越多的外部库——这些代码从哪里来?怎么安装?怎么保证你电脑上的版本和别人电脑上的一致?下一章,我们聊聊包管理器

Built with VitePress | Software Systems Atlas