自下而上编程
Original1993 年 1 月
(本文摘自《On Lisp》一书的介绍。)
程序的功能元素不应太大,这是编程风格的一个长期原则。如果程序的某些组件超出了易于理解的阶段,它就会变得非常复杂,错误很容易被隐藏,就像大城市隐藏逃犯一样。这样的软件将难以阅读、难以测试、难以调试。
根据这一原则,大型程序必须分成多个部分,程序越大,就必须分割得越多。如何分割程序?传统方法称为*自上而下的设计:*你说“程序的目的是做这七件事,所以我把它分成七个主要子程序。第一个子程序必须做这四件事,所以它又有四个自己的子程序”,等等。这个过程一直持续到整个程序具有正确的粒度级别——每个部分都足够大,可以做一些实质性的事情,但又足够小,可以被理解为一个单元。
经验丰富的 Lisp 程序员会以不同的方式划分他们的程序。除了自上而下的设计,他们还遵循一种可以称为自下而上的设计原则——改变语言以适应问题。在 Lisp 中,您不仅会根据语言编写程序,还会根据程序构建语言。在编写程序时,您可能会想“我希望 Lisp 有这样或那样的运算符。”所以你去编写它。后来你意识到使用新的运算符将简化程序另一部分的设计,依此类推。语言和程序一起发展。就像两个交战国之间的边界一样,语言和程序之间的边界被一次又一次地绘制,直到最终沿着山脉和河流停下,这是问题的自然边界。最终,您的程序看起来就像语言是为它设计的一样。当语言和程序很好地契合时,您最终会得到清晰、简短和高效的代码。
值得强调的是,自下而上的设计并不意味着只是以不同的顺序编写相同的程序。自下而上地工作时,您通常会得到不同的程序。您将得到的不是单一的整体程序,而是具有更多抽象运算符的更大的语言,以及用它编写的更小的程序。您将得到的不是门楣,而是拱门。
在典型的代码中,一旦你抽象出那些仅仅是记账的部分,剩下的部分就会短得多;你构建的语言越高,从上到下需要走的距离就越短。这带来了几个好处:
通过让语言完成更多的工作,自下而上的设计可以产生更小、更灵活的程序。更短的程序不必分成那么多组件,而更少的组件意味着程序更容易阅读或修改。更少的组件也意味着组件之间的连接更少,从而减少出错的机会。正如工业设计师努力减少机器中移动部件的数量一样,经验丰富的 Lisp 程序员使用自下而上的设计来减少程序的大小和复杂性。
自下而上的设计促进了代码的重用。当你编写两个或更多程序时,你为第一个程序编写的许多实用程序也将在后续程序中有用。一旦你掌握了大量实用程序,编写新程序只需要从原始 Lisp 开始所需的一小部分工作量。
自下而上的设计使程序更易于阅读。
这种抽象类型的实例要求读者理解通用运算符;功能抽象的实例要求读者理解专用子程序。[1]
自下而上地进行设计有助于理清程序设计思路,因为它会促使您始终留意代码中的模式。如果程序中两个相距遥远的组件在形式上相似,您就会注意到相似之处,并可能以更简单的方式重新设计程序。
自下而上的设计在 Lisp 以外的语言中也在一定程度上是可行的。只要你看到库函数,就会发现自下而上的设计正在发生。然而,Lisp 在这方面赋予了你更广泛的权力,而且语言的增强在 Lisp 风格中扮演着更重要的角色——以至于 Lisp 不仅仅是一种不同的语言,而是一种完全不同的编程方式。
这种开发风格确实更适合小团队编写的程序。然而,它同时也扩展了小团队所能做的事情的范围。在*《人月神话》一书中*,弗雷德里克·布鲁克斯提出,程序员团队的生产力并不是随着其规模线性增长的。随着团队规模的扩大,单个程序员的生产力会下降。Lisp 编程的经验告诉我们一个更为乐观的方式来表达这一规律:随着团队规模的缩小,单个程序员的生产力会上升。相对而言,小团队会获胜,仅仅是因为它的规模较小。当小团队也能利用 Lisp 所带来的技术时,它就能彻底获胜。
新功能:免费下载 On Lisp 。
[1] “但是,没有人能够在不理解你所有的新工具的情况下读懂这个程序。”要了解为什么这种说法通常是错误的,请参见第 4.8 节。