English Version

Progbot

1993年1月(本文选自《On Lisp》的引言)

编程风格的一个悠久原则是,程序的功能组件不应过大。如果程序的某个组件增长到难以理解的程度,它就会变成一团复杂性,如同大城市隐藏逃犯一样轻易地隐藏错误。这样的软件将难以阅读、难以测试、难以调试。

根据这一原则,大程序必须被分割成片段,程序越大,分割得越多。如何分割程序?传统的方法称为自顶向下设计:你说"程序的目的是做这七件事,所以我把它分成七个主要子程序。第一个子程序要做这四件事,所以它又有四个自己的子程序",依此类推。这个过程持续到整个程序达到适当的粒度级别——每个部分足够大以完成实质性工作,又足够小以作为单个单元被理解。

有经验的Lisp程序员以不同的方式分割他们的程序。除了自顶向下设计,他们遵循一个可以称为自底向上设计的原则——改变语言以适应问题。在Lisp中,你不仅向语言的方向编写程序,还向程序的方向构建语言。当你编写程序时,你可能会想"我希望Lisp有这样那样的运算符"。于是你就去写它。之后你意识到使用新的运算符会简化程序另一部分的设计,如此类推。语言和程序共同进化。如同两个交战国家之间的边界,语言和程序之间的边界被绘制和重绘,直到最终沿着山脉和河流——你问题的自然边界——安定下来。最终,你的程序看起来就像语言是为它而设计的。当语言和程序相互适应时,你最终得到的代码是清晰、小型和高效的。

值得强调的是,自底向上设计不仅仅意味着以不同的顺序编写相同的程序。当你自底向上工作时,你通常最终会得到不同的程序。你得到的不是单一的、整体的程序,而是一个具有更多抽象运算符的更大语言,以及在其中编写的更小程序。你得到的不是门楣,而是拱门。在典型的代码中,一旦抽象出仅仅是簿记的部分,剩下的内容就短得多;你构建的语言层次越高,从上到下需要走的距离就越短。这带来了几个好处:

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

自底向上设计促进代码重用。当你编写两个或更多程序时,你为第一个程序编写的许多实用程序在后续程序中也会很有用。一旦你获得了大量的实用程序基础,编写新程序可能只需要从头开始使用原始Lisp所需工作的一小部分。

自底向上设计使程序更容易阅读。这种类型的抽象实例要求读者理解通用运算符;功能抽象的实例要求读者理解专用子程序。

[1] 因为它使你总是在寻找代码中的模式,自底向上工作有助于澄清你对程序设计的想法。如果程序的两个相距较远的组件在形式上相似,你会注意到这种相似性,并可能以更简单的方式重新设计程序。

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

确实,这种开发风格更适合可以由小组编写的程序。然而,同时,它扩展了小组可以做的事情的极限。在《人月神话》中,Frederick Brooks提出程序员小组的生产力不会随着其规模线性增长。随着小组规模的增加,单个程序员的生产力会下降。Lisp编程的经验提出了一种更令人振奋的方式来表述这个定律:随着小组规模的减小,单个程序员的生产力会提高。相对而言,小组获胜只是因为它更小。当小组也利用Lisp使之成为可能的技术时,它可以完全获胜。

新:免费下载《On Lisp》。

[1] “但是如果不理解你所有的新实用程序,没有人能读懂这个程序。“要了解为什么这样的陈述通常是错误的,请参见第4.8节。