Loading...

自下而上的编程

Original

1993 年 1 月

(这篇文章摘自On Lisp* 的引言。)*

编程风格中有一个由来已久的原则,即程序的功能元素不应该太大。如果程序的某个组件发展到无法轻易理解的程度,它就会变成一个复杂的集合,像大城市一样容易隐藏错误,就像大城市一样容易隐藏逃犯。这样的软件将难以阅读、难以测试和难以调试。

根据这一原则,大型程序必须被分成多个部分,程序越大,就必须被分成越多部分。如何划分程序?传统的方法被称为自顶向下设计:你说“程序的目的是做这七件事,所以我把它分成七个主要子程序。第一个子程序必须做这四件事,所以它又会有四个自己的子程序”,等等。这个过程一直持续到整个程序都具有合适的粒度——每个部分都足够大,可以做一些实质性的事情,但又足够小,可以作为一个单元来理解。

经验丰富的 Lisp 程序员以不同的方式划分他们的程序。除了自顶向下设计,他们还遵循一个原则,可以称为自下而上设计——改变语言以适应问题。 在 Lisp 中,你不仅将你的程序向下写到语言中,你还会将语言向上构建到你的程序中。当你编写程序时,你可能会想“我希望 Lisp 有这样一个运算符”。所以你去写它。之后,你意识到使用新的运算符将简化程序另一部分的设计,等等。 语言和程序共同演变。 就像两个交战国家之间的边界一样, 语言和程序之间的界限被划定和重新划定, 直到最终它沿着山川河流, 你问题的自然边界, 最终你的程序看起来像是为它设计的语言。 当语言和 程序彼此契合良好时,你最终会得到清晰、简洁、高效的代码。

值得强调的是,自下而上设计并不意味着 只是以不同的顺序编写同一个程序。当你 自下而上工作时,你通常会得到一个不同的程序。 你不会得到一个单一的、整体的程序, 你会得到一个更大的语言,它有更多抽象的运算符, 以及用它编写的更小的程序。你不会得到一块横梁, 你会得到一个拱门。

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

通过让语言做更多工作,自下而上设计 产生的程序更小、更灵活。一个更短的 程序不必被分成那么多组件, 更少的组件意味着程序更容易阅读或 修改。更少的组件也意味着组件之间的连接更少, 因此,错误的可能性也更小。作为 工业设计师努力减少机器的运动部件数量, 经验丰富的 Lisp 程序员使用自下而上设计 来减少程序的大小和复杂性。

自下而上设计促进了代码重用。 当你编写两个 或多个程序时,你为第一个程序编写的许多实用程序 在随后的程序中也会有用。一旦你 获得了大量的实用程序,编写一个新的程序就可以 只需要原来的一小部分工作量,如果你不得不 从原始的 Lisp 开始。

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

这种类型的实例 抽象要求读者理解一个通用的运算符; 函数抽象的实例要求读者理解 一个专用子程序。[1]

因为它让你总是注意代码中的模式, 自下而上工作有助于澄清你对 程序设计的思路。如果程序的两个遥远组件 在形式上相似,你就会注意到这种相似性, 并可能以更简单的方式重新设计程序。

自下而上设计在 Lisp 以外的语言中也是可能的。 每当你看到库函数时, 自下而上设计正在发生。然而,Lisp 在这方面给了你更广泛的 权力,增强语言在 Lisp 风格中起着 更大的作用——如此之大,以至于 Lisp 不仅仅是一种不同的语言,而是一种完全不同的编程方式。

这种开发风格确实更适合 由小团队编写的程序。然而,同时,它扩展了小团队所能做的事情的界限。在人月神话中, 弗雷德里克·布鲁克斯 提出程序员团队的生产力 不会随着团队规模的线性增长而增长。随着团队规模的 增加,单个程序员的生产力 会下降。Lisp 编程的经验 表明了一种更乐观的表达这种规律的方式:随着团队规模的减小, 单个程序员的生产力会上升。 一个小团队相对来说会获胜,仅仅因为它 更小。当一个小团队也利用了 Lisp 使之成为可能的技巧时,它可以 彻底获胜

新:免费下载 On Lisp

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