“越差越好”的兴起

Richard Gabriel

我,以及几乎每一位 Common Lisp 和 CLOS 的设计者,都深受 MIT/Stanford 设计风格的影响。这种风格的本质可以用“正确之道”(the right thing)这个短语来概括。对这样的设计者来说,重要的是把以下所有特性都做好:

  • 简单性(simplicity):设计必须简单,无论是在实现上还是在接口上。接口简单比实现简单更重要。
  • 正确性(correctness):设计在所有可观察方面都必须正确。错误就是不被允许的。
  • 一致性(consistency):设计不能不一致。为了避免不一致,可以允许设计在简单性和完整性上稍微差一些。一致性和正确性同样重要。
  • 完整性(completeness):设计必须覆盖尽可能多的重要情况。所有合理预期到的情况都必须被覆盖。不能为了简单性而过度削弱完整性。

我相信大多数人都会同意,这些都是好的特性。我把使用这种设计哲学称为“MIT 路线”(MIT approach)。Common Lisp(以及 CLOS)和 Scheme 代表了设计与实现上的 MIT 路线。

“越差越好”的哲学只稍有不同:

  • 简单性:设计必须简单,无论是在实现上还是在接口上。实现简单比接口简单更重要。简单性是设计中最重要的考虑因素。
  • 正确性:设计在所有可观察方面都必须正确。但比起正确,简单稍微更重要。
  • 一致性:设计不能过度不一致。在某些情况下,可以为了简单性牺牲一致性;但与其引入实现复杂性或不一致,不如删掉那些处理较少见情况的设计部分。
  • 完整性:设计必须覆盖尽可能多的重要情况。所有合理预期到的情况都应该被覆盖。完整性可以为了任何其他品质而牺牲。事实上,只要实现简单性受到威胁,完整性就必须被牺牲。如果简单性得以保留,也可以牺牲一致性来获得完整性;尤其没有价值的是接口上的一致性。

早期 Unix 和 C 是这种设计流派的例子,我把使用这种设计策略称为“New Jersey 路线”(New Jersey approach)。我有意把“越差越好”的哲学刻意漫画化,是为了说服你:它显然是一种糟糕的哲学,New Jersey 路线也显然是一种糟糕的路线。

然而,我相信“越差越好”即使以这种稻草人版本(strawman form)出现,也比“正确之道”有更好的生存特性(survival characteristics);而且在软件中使用 New Jersey 路线,比 MIT 路线更好。

先让我重讲一个故事。这个故事说明,MIT/New Jersey 之间的区别是真实存在的,而且两种哲学的支持者确实相信自己的哲学更好。

两个著名人物,一个来自 MIT,另一个来自 Berkeley(但从事 Unix 工作),曾经碰面讨论操作系统问题。来自 MIT 的那个人熟悉 ITS(MIT AI Lab 的操作系统),也读过 Unix 源码。他感兴趣的是 Unix 如何解决 PC loser-ing 问题。所谓 PC loser-ing 问题,是指当用户程序调用一个系统例程(system routine)来执行某个耗时操作时,这个操作可能带有重要状态,比如 IO 缓冲区。如果操作期间发生中断,就必须保存用户程序的状态。由于调用系统例程通常只是一条指令,用户程序的 PC 并不能充分捕捉进程的状态。系统例程必须要么回退,要么继续前进。“正确之道”是回退,并把用户程序的 PC 恢复到调用系统例程的那条指令,这样用户程序在中断后恢复执行时,比如说,会重新进入系统例程。它被称为“PC loser-ing”,是因为 PC 被强行塞进了“loser mode”;在 MIT,“loser”是对“user”的一种亲昵称呼。

MIT 那个人没有看到任何处理这种情况的代码,于是问 New Jersey 那个人这个问题是如何处理的。New Jersey 那个人说,Unix 人知道这个问题,但解决办法是让系统例程总是运行到结束;不过有时会返回一个错误码,表示系统例程未能完成它的动作。于是,一个正确的用户程序必须检查错误码,以判断是否只是重新尝试这个系统例程。MIT 那个人不喜欢这个解决方案,因为它不是“正确之道”。

New Jersey 那个人说,Unix 的解决方案是正确的,因为 Unix 的设计哲学是简单性,而“正确之道”太复杂了。况且,程序员可以很容易地插入这个额外的测试和循环。MIT 那个人指出,实现是简单的,但功能的接口是复杂的。New Jersey 那个人说,Unix 选择了正确的权衡:也就是说,实现简单性(implementation simplicity)比接口简单性(interface simplicity)更重要。

MIT 那个人随后嘟囔说,有时候需要一个强硬的人才能做出嫩鸡,但 New Jersey 那个人没听懂(我也不确定自己听懂了)。

现在我要论证,“越差越好”更好。C 是一门为编写 Unix 而设计的编程语言,它采用 New Jersey 路线设计。因此,C 是一种容易为其编写像样编译器的语言,而且它要求程序员写出便于编译器解释的文本。有人把 C 称为华丽的汇编语言。早期 Unix 和 C 编译器的结构都很简单,容易移植,运行时需要的机器资源很少,并且能提供你希望从操作系统和编程语言中获得的大约 50%-80% 的东西。

在任何时刻,存在的计算机中都有一半低于中位数(更小或更慢)。Unix 和 C 在这些机器上运行得很好。“越差越好”的哲学意味着实现简单性具有最高优先级,这也意味着 Unix 和 C 很容易移植到这些机器上。因此,如果 Unix 和 C 支持的 50% 功能已经令人满意,人们就会预期它们会开始到处出现。它们确实如此,不是吗?

Unix 和 C 是终极的计算机病毒。

“越差越好”哲学还有一个额外好处:程序员会被训练成愿意牺牲一些安全性、便利性,并忍受一些麻烦,以换取良好的性能和适度的资源使用。采用 New Jersey 路线编写的程序,在小机器和大机器上都能很好工作;而且因为代码写在一个病毒之上,所以它是可移植的。

重要的是要记住,最初的病毒必须基本上是好的。如果是这样,只要它可移植,病毒式传播就是确定的。一旦病毒传播开,就会出现改进它的压力,可能会把它的功能完整度提升到接近 90%;但用户已经被训练成接受比“正确之道”更差的东西。因此,“越差越好”的软件首先会获得接受,其次会训练用户降低预期,第三会被改进到几乎接近“正确之道”。具体来说,尽管 1987 年的 Lisp 编译器已经和 C 编译器差不多好,但想让 C 编译器变得更好的编译器专家,远比想让 Lisp 编译器变得更好的专家多。

好消息是,到了 1995 年,我们会拥有一个好的操作系统和编程语言;坏消息是,它们会是 Unix 和 C++。

“越差越好”还有最后一个好处。由于 New Jersey 语言和系统其实不够强大,无法构建复杂的单体软件,大型系统就必须被设计成复用组件。因此,一种集成传统会自然生长出来。

那么,“正确之道”的表现如何?有两种基本情景:“庞大复杂系统”情景和“钻石般宝石”情景。

“庞大复杂系统”情景是这样的:

首先,需要设计“正确之道”。然后,需要设计它的实现。最后,才实现它。因为它是“正确之道”,所以它拥有几乎 100% 的期望功能;而实现简单性从来不是关注点,因此它需要很长时间才能实现。它庞大而复杂。它需要复杂的工具才能正确使用。最后 20% 的功能会消耗 80% 的努力,于是“正确之道”需要很长时间才能发布,而且只有在最先进的硬件上才能令人满意地运行。

“钻石般宝石”情景是这样的:

“正确之道”需要永远的时间来设计,但在这个过程中,它在每个时间点都相当小。要把它实现得运行很快,要么不可能,要么超出了大多数实现者的能力。

这两种情景分别对应 Common Lisp 和 Scheme。

第一种情景也对应经典人工智能软件。

“正确之道”常常是一块单体软件(monolithic software),但除了“正确之道”常常是按单体方式设计的之外,并没有别的原因。也就是说,这个特征只是偶然发生的。

从这里应该学到的教训是,先追求“正确之道”往往并不可取。更好的做法是先提供一半的“正确之道”,让它像病毒一样传播开来。一旦人们被它吸引,再花时间把它改进到“正确之道”的 90%。

一个错误的教训,是把这个寓言按字面理解,并得出 C 是 AI 软件正确载体的结论。50% 的解决方案必须在基本方向上是对的,而在这个案例中它并不是。

但是,人们至少可以得出一个结论:Lisp 社区需要认真重新思考自己在 Lisp 设计上的立场。关于这一点,我稍后会再说。

<rpg@lucid.com>

The Rise of "Worse Is Better"

1991 · Richard P. Gabriel

lucida 翻译

Tags: 软件工程, 编程语言, 经典重读