刚工作那会儿,我的调试方式只有一个字:试。改一行,跑一下,不行,再改一行,再跑。运气好半小时蒙对,运气不好耗到半夜,最后发现是配置文件里多了个空格。代码写久了才承认,调试这门手艺和写代码本身是两回事,得单独立规矩。

规矩一:复现不了,就不动手
以前接到 bug 反馈,我习惯直接扑向最可疑的那段代码。现在我会先问:这个 bug 能稳定复现吗?输入是什么,环境是什么,十次里出现几次?
不能稳定复现的 bug,你改完了也验证不了,等于没改。更要命的是偶发 bug 往往藏着并发、时序、缓存这类深层问题,瞎改一通反而把现场破坏掉。宁可花一小时搭复现环境,也不要花三小时在错误的代码里打转。
规矩二:用二分法缩小包围圈
程序是最讲因果的东西,bug 一定有一个引发它的位置。日志打到怀疑对象的入口和出口,看输入对不对、输出对不对,问题就锁进了半个区间;再往中间插一条,区间又小一半。
听起来笨,其实是最快的。一千行代码,十次定位就能锁到一行。我以前犯的错误是"看"代码找 bug——盯着屏幕逐行读,指望哪个错误自己跳出来。眼睛会骗你,你总倾向于看到自己以为写了的东西,而不是实际写了的东西。日志不会骗你。
规矩三:一次只改一处
这条是从无数次"改好了 A 搞坏了 B"里熬出来的。同时动三个地方,就算 bug 消失了,你也不知道是哪一刀起的作用;万一情况更糟,连回退都退不干净。改一处,测一遍,提交一次,每一步都是可逆的。
调试的本质不是猜,是审讯——用实验一步步缩小嫌疑范围,直到代码开口认罪。
还有个习惯顺带着养成了:每个修掉的 bug,在提交信息里写清楚根因,而不是只写"修复某某问题"。攒上一年再翻,你会发现自己的 bug 来来回回就是那几类——边界条件、空值、时区、编码。知道敌人长什么样,下次它就藏不住了。
调试不是玄学,玄学只是懒得做实验的借口。规矩立住,bug 就只是还没被问出来的答案。