先说一个反直觉的事实:写代码是调试里最简单、最不值钱的一步。真正难的、真正花时间的、真正决定谁是高手的,是动手改之前那段没人看得见的推理。就像破案,凶手落网只是最后一秒的动作,之前那些天的勘查、走访、推理、排除,才是侦探的本事。
这一章讲的就是这段推理。调试——找出程序为什么没按你预期那样工作、然后修好它——本质上不是「改代码」这个体力活,而是一场侦探游戏。
bug 是一具尸体,你得倒推它怎么死的
侦探到现场,看到的是结果:一具尸体、一扇撬开的窗、地上的脚印。他要倒着推回去:什么样的过程,才会留下这一组痕迹?
调试一模一样。你看到的是现象:页面白屏、数字算错了、每天凌晨三点服务就崩一次。你要倒推的是:这套系统到底经历了什么,才会吐出这个结果?
能不能倒推成功,取决于你脑子里有没有一张图——这套系统到底怎么运作的那张图。我们给它个名字:因果模型,就是你心里对「谁调用谁、数据从哪流到哪、这里改一下那里会怎样」的那套理解。
因果模型不是文档,也不是架构图。它是你脑子里的一台模拟器:你想象「如果用户点了这个按钮」,脑子里就能跑一遍,预测出会发生什么。调试,就是拿这台模拟器跑出来的预测,去对照现实里真实发生的事——哪里对不上,bug 就藏在那个缝里。
这就解释了一件很多人想不通的事:为什么越诡异的 bug 越难,而且专坑高手?因为诡异的 bug,恰恰发生在你因果模型的盲区里。如果你的模型是对的、是全的,这个 bug 根本不会「诡异」——你一眼就想到了。它之所以让你百思不得其解,正是因为你的模型在那个地方错了或缺了一块。你想不到那个原因,是因为在你脑子那张图上,那条路根本不存在。
难的从来不是 bug 本身,是你和真相之间那块你没意识到的认知盲区。
破案的方法:假设,然后想办法证明它是错的
好侦探不猜凶手,他建假设、做验证。调试的核心动作,是一个不停转的闭环:
- 观察现象——现在到底哪里不对,越具体越好。「慢」不算,「点提交后转圈 8 秒」才算。
- 提假设——基于你的因果模型,猜一个原因:「大概是那个数据库查询没走索引」。
- 设计实验——想一个办法,能把这个假设证伪。注意是证伪不是证实。
- 看新证据,修正模型——实验结果不管支持还是推翻假设,你脑子那张图都更新了一点,然后回到第一步。
为什么强调「证伪」?因为人有个坏毛病:认定一个原因后,会专挑支持它的线索看,忽略打脸的。侦探最怕先入为主锁定一个嫌疑人,然后所有证据都往他身上凑。破案的纪律是反过来的——你要主动去找那个能洗清嫌疑的证据。一个假设扛过了你拼命想推翻它的所有实验,它才可信。
一个真实感的例子:那个「只在周一早上出错」的 bug
假设你在查一个报表功能。现象很具体:用户反馈,一份「上周销售汇总」的报表,数字每周一早上打开是错的,到了下午自己就对了。代码看起来平平无奇,谁写的都挑不出毛病。
菜鸟的反应是盯着报表那段代码一行行读,读一天也读不出花来——因为bug 不在那段代码里,它在你没看的地方。
侦探怎么查:
- 先逼问现象。「周一早上错、下午对」这条线索太值钱了。什么东西跟「周一」和「时间」挂钩?这一问,就把范围从「整段代码」缩到了「和时间、和一周边界有关的逻辑」。侦探破案靠的就是这种——一条反常的细节,能砍掉九成的嫌疑人。
- 提假设一:「上周」的算法在周一算错了边界。设计实验:把系统时间手动改成某个周一早上,跑一遍,看它到底圈定了哪几天的数据。结果发现——它算的「上周」少了周日一整天。假设活下来了。
- 继续追:为什么少了周日?读那段算日期的代码,发现它用了「今天减 7 天」再取整到周。这里藏着一个只在运行时才暴露的东西:服务器用的是 UTC 时区,而用户在东八区。周一早上 8 点,用户觉得是周一,服务器的 UTC 时钟还停在周日下午。于是「周几」算错了,「上周」的边界跟着错。到了下午,两边跨过了同一天,就又对上了。
你看这个 bug:它不在报表代码里,在一个没人会盯着看的时区假设里。它之所以诡异,正因为写代码的人脑子里那张图默认「服务器时间就是用户时间」——盲区就在这。而破案靠的不是读代码,是那条「周一早上」的线索,加上一个能在现场把时钟拨过去、亲眼看结果的实验。
为什么 AI 能当助手,当不了侦探
AI 在这场游戏里是个好帮手,别小看它:它能飞快读完几千行日志、能列出「报表算错通常有哪几类原因」、能给你解释一段你没见过的陌生代码、能提出常规怀疑对象——「查查时区吧」它没准也说得出。这些相当于一个博学的、不知疲倦的助手,帮你把常规活儿包了。
但它当不了那个拍板的侦探,就卡在三件事上:
- 它不在现场。侦探得去案发现场。可你这套系统此刻真实跑成什么样——那台服务器的时区、那条日志里真实的时间戳、那个只在生产环境才复现的状态——这些线索只存在于运行时的现场,AI 看不见。它只能听你转述,而你转述的,恰恰漏掉了你自己没意识到的盲区。
- 它没有「你这套系统」的模型,只有「一般系统」的平均印象。AI 读过海量代码,脑子里那张图是所有系统揉在一起的平均值。可你的 bug 偏偏藏在你这套系统独有的、和别处都不一样的那个古怪角落里。平均印象帮你排除常见嫌疑,但破不了这种孤例。
- 那个闭环需要一个能负责的人来转。提假设、动手做实验、看新证据、修正模型——这个圈得有人在现场亲手拨时钟、亲眼看结果、并且为「改下去会不会引出新问题」负责。AI 能给你出主意,但它不能替你按下按钮、承担后果。侦探可以问遍所有专家,最后签字抓人的必须是他自己。
落点:AI 帮你写得越多,会查的人越稀缺
这里有个容易被忽略的转折。AI 越能帮你写代码,你手上就有越多不是你亲手写、你并不真懂的代码在跑。写的时候一路顺畅,出问题的那天,你面对的是一片自己脑子里没有对应因果模型的陌生地带。
能自动补全的代码在贬值,这没错。但正因为代码是补全出来的、是你没在脑子里过一遍的,读懂它、给它建因果模型、在它出事时把真凶揪出来的能力,反而更值钱了。调试考的从来不是打字,是判断力和因果推理——面对一个诡异现象,能不能问出那条对的线索、设计出那个能证伪的实验、并且认得出自己模型的盲区在哪。
这正是这本书的主线:凡是能被自动补全的都在贬值,凡是需要判断、需要在现场推理、需要担责的都在升值。调试,是这句话最集中的一次体现。AI 可以是你身边那个博学的助手,但站在现场、盯着那具「尸体」把因果链倒推出来的侦探,还得是你。