Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

自下而上编程

原文:Programming Bottom-Up
作者:Paul Graham 发表:1993
译者:Claude(baoyu-translate)

编程风格里有一条由来已久的原则:程序的功能单元不能太大。一旦某个组件膨胀到难以一眼看懂的程度,它就会变成一团复杂性,藏住错误就像大城市藏匿逃犯一样轻松。这样的软件难读、难测、难调。

按照这条原则,大程序必须切分成小块;程序越大,切得越细。那么怎么切呢?传统做法叫做自上而下设计(top-down design):你说“这个程序的目的是做这七件事,所以我把它分成七个主要子程序。第一个子程序要做四件事,所以它内部又分四个自己的子程序“,如此递归下去,直到整个程序粒度合适——每一部分都大到能做点实质性的事,又小到能作为单一单元被理解。

经验丰富的 Lisp 程序员切分程序的方式不一样。除了自上而下设计,他们还遵循另一条原则,可以叫做自下而上设计(bottom-up design)——改造语言去贴合问题。在 Lisp 里,你不只是把程序往下写到语言这一层,你还会把语言往上垒到程序这一层。写程序的时候你可能会想“要是 Lisp 有某某操作符就好了“。于是你就动手把它写出来。回头一看,你会发现用上这个新操作符可以简化程序的另一部分,于是又改一处;语言和程序就这样一起演化。这就像两个交战国家之间的边界,被反复划定、反复重画,最终落在山川河流这些天然分野上——也就是你这个问题真正的边界上。到最后你的程序看起来就像这门语言是为它量身打造的。当语言和程序彼此契合,你写出的代码就会清晰、紧凑、高效。

值得强调的是,自下而上设计不是指把同一个程序换个顺序写出来。当你自下而上地工作时,你通常会得到一个不一样的程序。你不再得到一个庞大单块的程序,而是得到一门更大的、带有更抽象操作符的语言,再加上一个用它写就的更小的程序。你不再得到一根过梁(lintel),而是得到一道拱(arch)。

在典型代码里,把那些只是做账目记录的部分抽象出去之后,剩下的就短得多;语言垒得越高,自上而下要走的路就越短。这样做有几个好处:

  1. 让语言承担更多工作,自下而上设计能产出更小、更灵活的程序。一个更短的程序不必切成那么多组件,组件少了就更容易读和改。组件少也意味着组件之间的连接少,出错的机会也就少。工业设计师努力减少机器的活动部件数量;经验丰富的 Lisp 程序员则用自下而上设计来减少程序的体量与复杂度。

  2. 自下而上设计促进代码复用。你写两个以上的程序时,第一个程序中写的工具函数有许多在后续程序里也用得上。一旦攒下了一片工具函数底层,再写新程序所花的力气就可能只剩从零开始用纯 Lisp 时的一小部分。

  3. 自下而上设计让程序更易读。这种抽象方式(指通用操作符)只要求读者理解一个通用操作符;而功能抽象(functional abstraction)要求读者理解一个专用子程序。

  4. 因为它逼你随时留意代码中的模式,自下而上工作有助于让你对程序设计的想法变得更清晰。如果程序中相距甚远的两个组件形式上很像,你就会注意到这个相似性,进而可能用一种更简洁的方式重新设计程序。

自下而上设计在 Lisp 之外的语言里也能做到一定程度。每当你看到库函数,都是自下而上设计在发生。但 Lisp 在这方面给你的能力要广得多,扩充语言在 Lisp 风格里所占的比重也大得多——大到 Lisp 不只是一门不一样的语言,而是一种完全不一样的编程方式。

确实,这种开发风格更适合那种能由小团队完成的程序。但与此同时,它也扩展了小团队能做到的边界。在《人月神话》(The Mythical Man-Month)里,弗雷德里克·布鲁克斯指出,一群程序员的生产力并不会随团队规模线性增长。团队越大,单个程序员的生产力反而越低。Lisp 编程的经验提供了一种更让人振奋的表述:团队越小,单个程序员的生产力越高。一个小团队,仅仅因为它小,相对而言就赢了。而当一个小团队还能用上 Lisp 提供的那些技术时,它就能赢得彻彻底底。