Debug
第六篇解决的是“在我电脑上跑不起来”。但还有另一半剧情:环境全对,程序也跑起来了——然后,报错了。或者更阴险的:不报错,但结果就是不对。
面对错误,新手的第一反应几乎总是同一种:盯着代码看一会儿,随手改点什么,再运行一次;不行,再改,再运行……运气好,改好了;运气不好,越改越乱。
先问一个扎心的问题:
你现在是在解决问题,还是在碰运气?
这一篇——系列的最后一篇——就讲:不靠运气,怎么把真正的凶手找出来。
一
先纠正一个心态:报错不是判决书,是线索。
很多人一看到满屏红色的报错就慌,其实你应该反过来想:程序出错的时候,拼尽全力给你留下了口供。它告诉了你三件事:
- 在哪错的:报错里带着文件名和行号,直接指认现场;
- 错成什么样的:错误的类型,比如“除以零了”“找不到这个文件”;
- 错的时候正在干什么:它当时一路调用过哪些函数,全列在下面。
以 Python 的报错为例:
Traceback (most recent call last):
File "main.py", line 8, in <module>
result = average(numbers)
File "main.py", line 3, in average
return sum(numbers) / len(numbers)
ZeroDivisionError: division by zero
读法是从下往上:最后一行最重要——ZeroDivisionError: division by zero,除以零了;往上,是出错的行:main.py 第 3 行;再往上,是“谁把它叫过来的”。程序不会说谎,报错里的每一行都是真的。排错的第一步,永远是把它完整读一遍——尤其是最后一行。
二
来看一个完整的剧本。你让 AI 写了个小程序:输入一行数字,输出它们的总和。你输入 1 2 3 4 5,预期 15,实际输出:14。
注意,它没报错——跑得干干净净,就是结果不对。这种“沉默的错误”比报错更常见,也更阴险。
本能反应是盯着代码看。但正确姿势是下面这条链:
发现异常(预期 15,实际 14)
↓
提出假设(差的正好是 1 —— 是不是数字 1 没被加进去?)
↓
设计验证(在累加处打印:现在加的是哪个数)
↓
假设证实,缩小范围(打印结果里 1 果然没出现 → 问题在循环)
↓
定位原因(循环是从第二个数开始的,起点写错了)
↓
修改
↓
再次运行验证(15,结案)
两个关键动作拆开说。
**第一,先猜“哪类问题”,再动手。**14 比 15 差 1,五个数的总和是 15——“差 1”这个细节本身就是线索。于是有了第一个假设:数字 1 没被加进去。注意,这不是瞎猜:瞎猜是“要不我改改循环试试”,假设是“我怀疑 1 没被加上,所以我去验证 1 有没有被加上”。前者改了也不知道说明什么,后者验证完一定有结论。
**第二,让中间过程开口说话。**怎么验证“1 有没有被加上”?在累加的地方加一句打印:
正在加:2
正在加:3
正在加:4
正在加:5
不用读代码,数据自己招了:1 真的没出现。顺藤摸瓜找到循环那一行——for i in range(1, len(nums))——它从第二个数开始跑。改成从第一个开始,再运行一次:15。
全程没有一步是碰运气,每一步都知道自己在验证什么。最后那句“15”也不是“应该对了”,而是验证过了。
三
把上一节的方法推广成三条通用战术:
**报错的 bug 是好 bug。**它自己举手了,你顺着报错查就行。真正要警惕的是沉默的:结果不对、网页空白、数据少了、页面比昨天慢。遇到“现象不对”,回到第一篇的老路:从现象追到原因,沿着链条一环一环问“是这里吗”。
**在关口放哨兵。**那句打印,专业叫法是“调试输出”,行话叫 print 大法:在程序的几个关键位置各放一句打印,看数据走到哪里就不对劲了。位置放得准的话,一次打印就能把嫌疑范围砍掉一半——在链条中间打印一次,就知道问题在前半段还是后半段。规模大了,这个思路的工业化版本叫“日志”,但你先用 print 就够了。
**一次只动一个变量。**改之前先 git commit 存个档(第四篇的后悔药,排错时比平时更值钱),然后一次只改一个地方、运行一次、看结果变化。同时改三处然后“好了”,你永远不知道是哪一处起了作用——这是第六篇“控制变量”在排错现场的样子。
四
最后,说怎么和 AI 一起排错。这可能是你用得最多的一节。
给 AI 报错,有个黄金三件套:
- 报错原文——原样复制,别手抄,别省略,别截图转述;
- 你做了什么——输入了什么、跑了哪条命令、点了哪里;
- 期望什么、实际什么——你以为会发生 A,实际发生了 B。
反面教材是那句最常见的话:“程序报错了,怎么办?”——AI 只能反过来问你一圈,一问一答烧掉五轮。而三件套给足了,它的第一轮命中率高得多。
再深一层看:AI 排错,内部走的也是“假设 → 验证 → 缩小范围”这条链。你给的信息越具体,它的假设就越准——你们其实在做双人舞,它负责猜,你负责喂线索、验收结果。它说“改好了”,别直接信,自己跑一遍——第三篇说过的:执行和验收,是你的事。
五
到这里,这个系列要讲的东西讲完了。最后把七篇的账一起算一算:
| 篇 | 主题 | 带走的一句话 |
|---|---|---|
| 一 | 网络请求 | 看到现象,不要停在现象;继续问“为什么”。 |
| 二 | 服务器 | 从目标出发,反推实现目标需要什么。 |
| 三 | Linux | 一个复杂目标,可以拆成一系列具体行动。 |
| 四 | Git | 解决问题时,不仅要想怎么成功,还要提前想好:失败了怎么办。 |
| 五 | API | 不一定要理解整个系统,先找到和它交互的接口。 |
| 六 | 环境与依赖 | 当变量太多时,尝试控制变量。 |
| 七 | Debug | 不要靠猜解决问题,用假设和验证定位问题。 |
表面上,这七篇讲的是网络、服务器、命令行、Git、API、环境、排错。但把它们排在一起你会发现,真正教的是同一件事:
面对一个问题,我应该怎么办?
从“为什么”开始,到“为什么”结束:Debug 就是第一篇那句话的完全体。以后你遇到“打不开”“跑不起来”“结果不对”,别停在现象,也别急着碰运气——提出假设,设计验证,让证据自己开口。
祝你玩得开心,改坏得放心。