自底向上的编程
1993
(本文选自《On Lisp》一书的引言。)
编程风格中有一个长期坚持的原则:程序中的功能元素不应过于庞大。如果程序的某个组件增长到难以理解的地步,它就会变成一团复杂体,像大城市藏匿逃犯一样轻易地掩盖错误。这样的软件将难以阅读、难以测试、难以调试。
依据这一原则,大型程序必须被分割成若干部分,程序越大,分割得越细。如何分割程序?传统方法称为自顶向下设计:你说“这个程序的目的是完成这七件事,所以我将其划分为七个主要的子程序。第一个子程序需要完成这四件事,因此它又有四个子程序,”如此类推。这个过程持续到整个程序达到合适的粒度——每一部分足够大以承担实质性的工作,又足够小以作为一个独立单元被理解。
经验丰富的Lisp程序员划分程序的方式有所不同。除了自顶向下设计,他们还遵循一个可以称为自底向上设计的原则——改变语言以适应问题。在Lisp中,你不只是将程序向下书写至语言层面,你也在将语言向上构建至程序层面。在编写程序时,你可能会想:“我希望Lisp有这样一个操作符。”于是你去实现它。之后你会意识到,使用新的操作符可以简化程序另一部分的设计,如此循环。语言和程序共同演进。就像两个交战国家的边界,语言与程序之间的分界被不断划定又重绘,直到最终沿着山川河流——你的问题的自然边界——稳定下来。最终,你的程序看起来就像是为它量身定制的语言。当语言与程序彼此契合时,你得到的代码将是清晰、紧凑且高效的。
值得强调的是,自底向上设计并不只是以不同的顺序编写同一个程序。当你自底向上工作时,你最终得到的往往是一个不同的程序。取而代之的是,你将获得一个更大型的语言,带有更抽象的运算符,以及一个用这种语言编写的更小的程序。你将得到一座拱门,而不是一块过梁。
在典型的代码中,一旦你将那些仅属于簿记性质的部