前七章讲的一圈——建模、检验、读误差——听起来很顺。但真到了现实里,你会撞上一堵墙:要撞的那个东西太大、太乱,根本没法整个儿去撞。
「我的理论对不对?」——这问题太大,没法一下回答。「我这个十万行的程序为什么这么慢?」——这问题太乱,盯着它发呆一年也理不清。「我最近为什么总是累?」——睡眠、饮食、压力、缺不缺运动全搅在一起。一个大问题,你没法直接问现实;现实只会回你一团同样含糊的东西。
大问题不可检验,小问题才可以
出路只有一个:把大问题拆成一堆小问题,每个小问题都小到能单独拿去撞现实、能得到一个干脆的回答。这个古老的招数叫分而治之——先分,再各个击破。
为什么非拆不可?回到第五章那条铁律:一个检验只有在隔离出一样东西时才教得会你什么。你要是一个检验同时压着二十条假设,它失败时,你根本不知道是二十条里的哪一条崩了——第七章那个宝贵的「误差指针」,指向的是一团二十个东西的糨糊,等于没指。拆分,就是让误差的箭头能真正落到某一个具体的砖块上。
换个说法:「整个程序错了」不是一条有用的信息,「第 47 行那个函数,喂进 3 应该吐出 9,结果吐出 6」才是。后者小到你一眼能判对错,还小到你知道该去改哪儿。把问题拆小,不是为了整齐,是为了让每一小块都可判定——现实能对它给出清清楚楚的「对」或「错」。
三个行当,同一把刀
程序员把这件事做成了日常。与其「跑一遍整个应用,看结果像不像样」,他们给每个小函数单独写一个单元测试——喂给它一个已知的输入,检查它吐出的是不是那个该有的输出。一个函数就是一块砖,单独验过。哪天出事,几十个测试里红的那一个,直接把坏砖指给你看。他们还把系统拆成一个个模块,每块只管一件事、能单独测——这样才敢一块块地信、一块块地换。
科学家的控制实验是同一把刀的另一面:一个复杂现象背后往往有好几套机制搅在一起,科学家把它们一个个隔离出来,一次只钉死一个。厨子也一样——一道难菜,先把酱汁调对、把火候练准,各个部件单独过关,最后才合在一起。你以为高手是「一下把整件大事做对」,其实他们是把大事拆成了一串「小到不可能做错」的小事。
刀要砍在骨缝上
拆分有高下之分,讲究全在从哪儿下刀。
好的拆法,砍在「骨缝」上——切出来的小块彼此尽量不搅在一起:你能单独动其中一块、单独测其中一块,不牵动别的。坏的拆法,块与块之间藕断丝连,你想测这一块,却发现它偷偷依赖着那三块,于是根本没法单独检验——等于没拆。
怎么切出干净的块?靠约定接口:给每一块规定清楚「你只要保证:给你这样的输入,你就还我那样的输出」,至于你内部怎么实现,别人不用管。有了这个承诺,你就能拿这个承诺去单独考它,完全不必管其余部分。这是工程最厉害的地方,也是科学「隔离变量」的同一个念头:先把边界划清楚,块与块之间才不会互相污染检验结果。
但拼起来,可能又是另一回事
得留一句警告,免得你把这把刀当成万能的:每一块都单独验对了,拼起来仍然可能出错。
每个零件都合格,装成整机却嗡嗡乱抖——因为有些问题只在零件之间的配合里才冒出来,在任何单个零件里都找不到。程序员对此深有体会:函数一个个都过了测试,一联调就崩,因为它们凑到一起时的相互作用,是任何单元测试都没覆盖到的。
所以拆分是必要的,但不充分:你既要单独验每一块,也要回过头验一次拼起来的整体。这背后其实是「还原论」的一条边界——把整体拆成部件、再逐个理解,威力巨大,但「整体等于部件之和」并不总成立。有些性质是拼出来的,只在更高的层次上才存在。这个话题第十章还会碰到。
卡住时,就问这一句
把这一章拧成一个动作。下次你被一个「就是不行 / 就是搞不懂」的大问题卡住,别对着它硬耗,先问:
「这件大事,是靠哪几条更小的假设撑起来的?其中哪一条,我现在就能花很小的代价单独验一下?」
拆出这一串小假设,再用第五章的二分法,从最可疑、最容易验的那条开刀。你会发现,绝大多数「无从下手」的难题,一旦拆成小块,就变成了一串「一看就知道怎么验」的小活儿。
拆到这么细,还剩一个问题:很多块的对错,光靠「看一眼」是判不准的——菜咸不咸、程序快不快、这一版比上一版好没好,都需要一个更硬的东西来裁。那就是下一章的主角:数字。为什么一个粗糙的数字,往往胜过一个精确的感觉。