Loading...

自下而上的编程

Original

1993年1月

(这篇文章来自On Lisp的介绍部分。)

编程风格的一个长期原则是,程序的功能元素不应该太大。如果程序的某个组件超出了易于理解的范围,它就会成为一个复杂的庞然大物,很容易隐藏错误,就像一座大城市可以隐藏逃犯一样。这样的软件将很难阅读、测试和调试。

为了遵循这一原则,大型程序必须被划分为多个部分,而且程序越大,划分就越多。如何划分程序?传统的方法称为自上而下设计:你说"程序的目的是完成这七件事,所以我将其划分为七个主要子程序。第一个子程序需要完成这四件事,所以它又会有四个自己的子程序",依此类推。这个过程一直持续到整个程序达到合适的粒度 -- 每个部分足够大,可以完成一些实质性的工作,但又足够小,可以被理解为一个单独的单元。

有经验的Lisp程序员以不同的方式划分他们的程序。除了自上而下设计,他们还遵循一个可以称为自下而上设计的原则 -- 改变语言以适应问题。

在Lisp中,你不仅要将程序写向语言,还要将语言建立向你的程序。在编写程序时,你可能会想"我希望Lisp有这样或那样的操作符。"所以你就去写它。之后你会意识到使用新的操作符会简化程序的另一部分的设计,依此类推。语言和程序一起演化。就像两个交战国家之间的边界一样,语言和程序之间的界限会被反复划定和重新划定,直到最终沿着山脉和河流,即问题的自然边界而定下来。最终,你的程序看起来就像是为它而设计的语言。当语言和程序很好地契合时,你最终会得到一个清晰、简洁和高效的代码。

需要强调的是,自下而上设计并不意味着以不同的顺序编写同样的程序。当你采用自下而上的方式工作时,通常会得到一个不同的程序。你不会得到一个单一的、单一的程序,而是会得到一个更大的语言,拥有更多的抽象操作符,以及用它编写的更小的程序。你会得到一个拱形,而不是一个楣门。

在典型的代码中,一旦你抽象出那些仅仅是簿记的部分,剩下的部分会变得更短;你越是向上构建语言,从顶部到它的距离就越短。这带来了几个优点:

通过让语言承担更多工作,自下而上的设计产生了更小、更敏捷的程序。一个更短的程序不需要被划分为那么多组件,组件更少意味着程序更容易阅读或修改。组件更少也意味着组件之间的连接更少,因此出错的机会也更少。正如工业设计师努力减少机器中的活动部件一样,有经验的Lisp程序员使用自下而上的设计来减少他们程序的大小和复杂性。

自下而上的设计促进了代码的重用。当你编写两个或更多个程序时,你为第一个程序编写的许多实用程序也会在后续程序中有用。一旦你积累了大量的实用程序基础,编写一个新程序只需要付出很小一部分的努力,而不是从原始的Lisp开始。

自下而上的设计使程序更易于阅读。

这种抽象的实例要求读者理解一个通用的操作符;而函数抽象的实例要求读者理解一个特殊用途的子程序。[1]

因为它使你时刻关注代码中的模式,自下而上的工作有助于澄清你对程序设计的想法。如果程序的两个远程组件在形式上相似,你就会被引导去注意这种相似性,也许会以一种更简单的方式重新设计程序。

在Lisp以外的语言中,也可以在一定程度上实现自下而上的设计。每当你看到库函数时,自下而上的设计就在发生。然而,Lisp为你提供了更广泛的权力,在Lisp风格中,增强语言在相当大程度上发挥作用 -- 以至于Lisp不仅仅是一种不同的语言,而是一种完全不同的编程方式。

这种开发方式确实更适合由小组编写的程序。但与此同时,它也扩展了小组能够完成的工作的限度。在The Mythical Man-Month中,Frederick Brooks提出,程序员群体的生产率并不会随着群体规模的线性增长。随着群体规模的增加,个人程序员的生产率会下降。Lisp编程的经验提出了一种更乐观的表述方式:随着群体规模的减小,个人程序员的生产率会上升。一个小组凭借其规模的优势就可以获胜。当一个小组还利用Lisp所提供的技术时,它就可以彻底获胜

新: 免费下载 On Lisp

[1] "但没有人能在不理解所有新实用程序的情况下阅读程序。" 要了解为什么这种说法通常是错误的,请参见第4.8节。