我的抽象掩盖了那些我真正需要看见的东西
我钟爱干净的抽象。把某个杂乱而复杂的东西包进一个整洁的接口里,让它把混乱藏起来,于是你可以在更高的层面思考,忘掉底下的细节——这其中有一种实实在在的审美愉悦。所以我不断地伸手去抓抽象——早早地、急切地、而且远远过于频繁。而令人意外的是,其中相当多的时候,我抓住的那个抽象根本没有管理任何复杂度。它恰恰藏起了我最需要看见的那些东西,还为这份”特权”额外向我收取了一层新的困惑作为代价。
这是一个惨痛的教训,恰恰是因为抽象本身确确实实是好的、确确实实是强大的。错误不在于使用它。错误在于错误地使用它,并且把我对干净表面的喜爱误当成了良好的工程判断力。
一个好的抽象本应做到什么
值得把抽象的用途说清楚,因为整个失败其实是对某个真实之物的扭曲。一个好的抽象会把你此刻不需要去想的细节藏起来,好让你在更高的层面上推理,而不必把整团混乱一次性全装进脑子里。它是整个工程领域中最强大的工具之一——可以说就是那个工具,是它让你得以构建出复杂到无法一次性全部理解的系统。
当一个抽象奏效时,它几乎是隐形的。你使用它而不去想它底下是什么,就像你开车时不会去想内燃机的运转。它把你有限的注意力解放出来,留给你真正在意的那个层面。这就是那个梦想,是个好梦,而且是真实的。它也正是让那种失败模式如此诱人的原因:每一个抽象都许下这个承诺,而只有一部分真正兑现。
错误的抽象反而会做什么
一个糟糕的抽象做的事情与好的抽象恰好相反,而且它在做这件事时,从外面看上去几乎一模一样。它不是把你不需要的细节藏起来,而是把你需要的细节藏起来。它把边界划错了地方,于是那些你恰恰需要去推理的东西,最终落到了一堵你亲手砌起来的墙的另一侧,突然就够不着了。
而且它在第一桩罪之上又犯下第二桩。它并没有真正消除底层的复杂度——那份复杂度仍然在那里,原封未动,在表面之下继续运作。这个抽象只不过是在它之上又叠加了一层新的东西。于是现在,你要理解的不是一个复杂的东西,而是两个:底下那团杂乱的现实,以及你铺在其上的那个不太对劲的抽象,外加它们之间那永远令人困惑的关系。你本想减少自己要去思考的东西,结果反倒把它增加了。
过早的抽象:在理解之前就做出承诺
我这个错误最常见的形式就是抽象得太早。我会在自己还没真正把问题理解到足以知道正确结构究竟该是什么样之前,就先把那个干净、通用、优雅的结构搭建出来。它感觉像是良好的、有前瞻性的设计。它通常恰恰相反。
抽象,从根本上说,是对某个特定形状的一种承诺——是关于边界划在哪里、什么被藏起来、什么被暴露、各部分如何关联的一个决定。而在你理解你所要塑形的那个东西之前就承诺一个形状,是把错误形状锁死下来的一条可靠途径。更糟的是,一旦它就位,你就开始在它之上构建,而你构建的每一个新东西都加深了你对一个靠猜测而非靠学习得来的结构的投入。过早的抽象不仅仅是有出错的风险;它还会主动地把自己的错误固化下来。
有泄漏的抽象会逼你同时调试两样东西
有一种特殊的折磨,是专门留给那些隐藏得不完美的抽象的——那些大体上能用、直到突然不能用为止的抽象。当某个东西在那个抽象本应遮盖的现实底层出了问题时,泄漏会逼你同时去理解两个层面:你被许诺可以安心在其中思考的那个干净表面,以及它没能完全藏住的、底下那团杂乱。
这种折磨格外令人精疲力竭。抽象的全部意义本是让你只去想一件事。如今一次故障却把你拖进了要去想两件事的境地,外加它们之间的映射,外加这个 bug 究竟落在哪一层的问题。你在同时调试这个抽象和它所抽象的那个东西,而那个本应帮你忙的抽象,如今成了横在你与问题之间的一道额外障碍。一个有泄漏的抽象可能比完全没有抽象还要糟糕,因为至少在没有抽象的情况下,你也只有一层东西会把你搞糊涂。
一层你并不需要的层级的代价
我先前没有妥善考虑到的一点是:每一层抽象同时也是一层间接——一件横亘在你与实际发生之事之间的东西。当一切运转正常时,那段距离就是好处;正是它让你得以不去想更低的那个层面。但一旦你需要去理解或调试,同样的那段距离就变成了纯粹的代价。每一层都是你必须再去查看的一个地方,是你必须在脑中再做一次的一道转译,是问题可能藏身之处的又一个候选位置。
所以,一个并没有真正为自己挣回价值的抽象并非中性的。它是一笔常设的税,每当你不得不对系统进行推理时就被征收一次,征收的代价是为了换取一份你最终发现自己并不需要的便利。我一直急切地在添加这些税,被那些干净的表面所迷住,却没有注意到自己为其中大多数付了费,却只从其中一部分获得了收益。
从具体之物中挣得你的抽象
最终帮到我的那条纪律,几乎是我本能的彻底反转。不要从一个关于你最终会需要什么的猜测出发,预先强加抽象。要去挣得它们,从具体而反复的经验中。先把那个具体、丑陋、特定的东西实打实地做上几遍。去感受真正的接缝在哪里——天然的边界实际落在何处,哪些细节确实聚成一团、哪些则不然。然后,且只有到那时,当现实已经向你展示出它的关节之后,再沿着它实际划出的那些线去做抽象。
我后来才明白,好的抽象是从具体之物中被发现的,而不是被强加到它之上的。它是对你已经深刻理解之物的一种压缩,而不是一条绕开理解它们的捷径。我用那种方式构建出来的抽象——很晚、很不情愿、在具体之物已经教会我它们该归属何处之后才建起来的——才是真正立得住的那些。而我早早地、出于聪明与审美而构建的那些,则是我在接下来的几个月里一直与之搏斗的那些。
更深的教训:抽象管理复杂度,但它并不替代理解
这一切归结起来,是一种关于抽象用途的混淆。我一直把它当作理解问题的替代品来抓取——仿佛一个干净的接口就能让我跳过实际去搞懂底下那团杂乱之物的那份苦功。它不能。抽象是一个用来管理你已经理解的复杂度的工具,而不是用来回避去理解它的那份功夫的。
你没法靠抽象把自己从”没搞懂某样东西”里救出来。你能做到的,只是把你自己的困惑藏到一个看上去更整洁的表面之下,而它会在那里耐心等候,日后伺机伏击你,只不过此时它已披上了一个干净的接口。先去理解那个东西。再去抽象。而当你抽象时,要沿着理解所揭示出的那些接缝去划边界,而不是那些在你还不知道任何东西实际身处何处之前、仅仅看上去优雅的边界。
— 没有信号,没有回报,这不是投资建议。