### 14.梦寐以求的编程语言
> 一心让臣民行善的暴君可能是最专制的暴君。
> ——C.S.LEWIS(1898—1963,英国小说家)
我的朋友曾对一位著名的操作系统专家说他想要设计一种真正优秀的编程语言。那位专家回答,这是浪费时间,优秀的语言不一定会被市场接受,很可能无人使用,因为语言的流行不取决于它本身。至少,那位专家设计的语言就遭遇到了这种情况。
那么,语言的流行到底取决于什么因素呢?流行的语言是否真的值得流行呢?还有必要尝试设计一种更好的语言吗?如果有必要的话,怎样才能做到这一点呢?
为了找到这些问题的答案,我想我们可以观察黑客,了解他们使用什么语言。编程语言本来就是为了满足黑客的需要而产生的,当且仅当黑客喜欢一种语言时,这种语言才能成为合格的编程语言,而不是被当作“指称语义”(denotational semantics)或者编译器设计。
### 流行的秘诀
没错,大多数人选择某一种编程语言,不是因为这种语言有什么独特的特点,而是因为听说其他人使用这种语言。但是我认为,外界因素对于编程语言的流行其实没有想象中那么大的影响力。我倒是觉得,问题出在对于什么是优秀编程语言,黑客的看法与大多数的语言设计者不一样。
黑客的看法其实比语言设计者的更重要。编程语言不是数学定理,而是一种工具,为了便于使用,它们才被设计出来。所以,设计编程语言的时候必须考虑到人类的长处和短处,就像设计鞋子的时候必须符合人类的脚型。如果鞋子穿上去不舒服,无论它的外形多么优美,多么像一件艺术品,你也只能把它当作一双坏鞋。
大多数程序员也许无法分辨语言的好坏。但是,这不代表优秀的编程语言会被埋没,专家级黑客一眼就能认出它们,并且会拿来使用。虽然他们人数很少,但就是这样一小群人写出了人类所有优秀软件。他们有着巨大的影响力,他们使用什么语言,其他程序员往往就会跟着使用。老实说,很多时候这种影响力更像是一种命令,对于其他程序员来说,专家级黑客就像自己的老板或导师,他们说哪种语言好用,自己就会乖乖地跟进。
专家级黑客的看法不是决定一种语言流行程度的唯一因素,某些古老的软件(Fortran和Cobol的情况)和铺天盖地的广告宣传(Ada和Java的情况)也会起到作用。但是,我认为从长期来看,专家级黑客的看法是最重要的因素。只要有了达到“临界数量”(critical mass)的最初用户和足够长的时间,一种语言可能就会达到应有的流行程度。而流行本身又会使得这种优秀的语言更加优秀,进一步拉大它与平庸语言之间的好坏差异,因为使用者的反馈总是会导致语言的改进。你可以想一下,所有流行的编程语言从诞生至今的变化有多大。Perl和Fortran是极端的例子,除它们两个之外,甚至就连Lisp都发生了很大的变化。
所以,即使不考虑语言本身的优秀是否能带动流行,我想单单流行本身就肯定会使得这种语言变得更好,只有流行才会让它保持优秀。编程语言的最高境界一直在发展之中。虽然语言的核心功能就像大海的深处,很少有变化,但是函数库和开发环境之类的东西就像大海的表面,一直在汹涌澎湃。
当然,黑客必须先知道这种语言,才可能去用它。他们怎么才能知道呢?就是从其他黑客那里。所以不管怎样,一开始必须有一群黑客使用这种语言,然后其他人才会知道它。我不知道“一群”的最小数量是多少,多少个黑客才算达到“临界数量”呢?如果让我猜,我会说20人。如果一种语言有20个独立用户,就意味这20个人是自主决定使用这种语言的,我觉得这就说明这种语言真的有优点。
达到这一步并非易事。如果说用户数从0到20比从20到1000更困难,我也不会感到惊讶。发展最早的20个用户的最好方法可能就是使用特洛伊木马:你让人们使用一种他们需要的应用程序,这个程序偏巧就是用某种新语言开发的。
### 外部因素
我们得先承认,确实有一个外部因素会影响到语言的流行。一种语言必须是某一个流行的计算机系统的脚本语言(scripting language),才会变得流行。Fortran和Cobol是早期IBM大型机的脚本语言。C是Unix的脚本语言,后来的Perl和Python也是如此。Tel是Tk的脚本语言,Visual Basic是Windows的脚本语言,(某种形式的)Lisp是Emacs的脚本语言,PHP是网络服务器的脚本语言,Java和JavaScript是浏览器的脚本语言。
编程语言不是存在于真空之中。“编程”其实是及物动词,黑客一般都是为某个系统编程,在现实中,编程语言总是与它们依附的系统联系在一起的。所以,如果你想设计一种流行的编程语言,就不能只是单纯地设计语言本身,还必须为它找到一个依附的系统,而这个系统也必须流行。除非你只想用自己设计的语言取代那个系统现有的脚本语言。
这种情况导致的一个结果就是,无法以一种语言本身的优缺点评判这种语言。另一个结果则是,只有当一种语言是某个系统的脚本语言时,它才能真正成为编程语言。如果你对此很吃惊,觉得不公平,那么我会跟你说不必大惊小怪。这就好比大家都认为,如果一种编程语言只有语法规则,没有一个好的实现(implementation),那么它就不能算完整的编程语言。这些都是很正常很合理的事情,编程语言本来就该如此。
当然,编程语言本来就需要一个好的实现,而且这个实现必须是免费的。商业公司愿意出钱购买软件,但是黑客作为个人不会愿意这样做,而你想让一种语言成功,恰恰就是需要吸引黑客。
编程语言还需要有一本介绍它的书。这本书应该不厚,文笔流畅,而且包含大量优秀的范例。布赖恩·柯尼汉和丹尼斯·里奇合写的《C程序设计语言》(C Programming Language)就是这方面的典范。眼下,我大槪还能再加一句,这一类书籍之中必须有一本由O'Reilly公司出版发行。这正在变成是否能吸引黑客的前提条件了。
编程语言还应该有在线文档。事实上,在线文档可以当作一本书来写,但是目前它还无法取代实体书。实体书并没有过时,它们读起来很方便,而且出版社对书籍内容的审核是一种很有用的质量保证机制(虽然做得很不完美)。书店则是程序员发现和学习新语言的最重要的场所之一。
### 简洁
假定你的语言已经能够满足上面三项条件——一种免费的实现,一本相关书籍,以及语言所依附的计算机系统——那么还需要做什么才能使得黑客喜欢上你的语言?
黑客欣赏的一个特点就是简洁。黑客都是懒人,他们同数学家和现代主义建筑师一样,痛恨任何冗余的东西或事情。有一个笑话说,黑客动手写程序之前,至少会在心里盘算一下哪种语言的打字工作量最小,然后就选择使用该语言。这个笑话其实与真实情况相差无几。就算这真的是个笑话,语言的设计者也必须把它当真,按照它的要求设计语言。
简洁性最重要的方面就是要使得语言更抽象。为了达到这一点,首先你设计的必须是高级语言,然后把它设计得越抽象越好。语言设计者应该总是看着代码,问自己能不能使用更少的语法单位把它表达出来。如果你有办法让许多不同的程序都能更简短地表达出来,那么这很可能意味着你发现了一种很有用的新抽象方法。
不要觉得为用户着想就是让他们使用像英语一样又长又啰嗦的语法。这是不正确的做法,Cobol就是因为这个毛病而声名狼藉。如果你让黑客像下面这样求和:
`add x to y giving z`
而不是写成:
`z=x+y`
那么你就是在侮辱黑客的智商,或者自己作孽了。
简洁性是静态类型语言的力所不及之处。不考虑其他因素时,没人愿意在程序的头部写上一大堆的声明语句。只要计算机可以自己推断出来的事情,都应该让计算机自己去推断。举例来说,hello-world本应该是一个很简单的程序,但是在Java语言中却要写上一大堆东西,这本身就差不多可以说明Java语言设计得有问题了^。
^「hello-world程序的唯一作用就是显示出“Hello,world!”这句话。使用Java语言,你需要这样写:
`public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}`
如果你从来没有接触过编程,看到上面的代码可能会很奇怪,让计算机显示一句话为什么要镐得这么复杂?有意思的是,资深程序员的反应与你一样。」
单个的语法单位也应该很简短。Perl和Common Lisp在这方面是两个不同的极端。Perl的语法单位很短,导致它的代码可以拥挤得让人无法理解,而Common Lisp内置运算符的名称则长得可笑。Common Lisp的设计者们可能觉得文本编辑器会帮助用户自动填写运算符的长名称。但是这样做的代价不仅是增加了打字的工作量,还包括提高了阅读代码的难度,以及占用了更多的显示器空间。
### 可编程性(Hackability)
对黑客来说,选择编程语言的时候,还有一个因素比简洁更重要,那就是这种语言必须能够帮助自己做到想做的事。在编程语言的历史上,防止程序员做出“错误”举动的措施多得惊人。这是语言设计者很自以为是的危险举动,他们怎么知道程序员该做什么不该做什么?我认为,语言设计者应该假定他们的目标用户是一个天才,会做出各种他们无法预知的举动,而不是假定目标用户是一个笨手笨脚的傻瓜,需要别人的保护才不会伤到自己。如果用户真的是傻瓜,不管你怎么保护他,他还是会搬起石头砸自己的脚。你也许能够阻止他引用另一个模块中的变量,但是你没法防止他日日夜夜不知疲倦地写出结构混乱的程序去解决完全错误的问题。
优秀程序员经常想做一些既危险又令人恼火的事情。所谓“令人恼火”,我指的是他们会突破设计者提供给用户的外部语义层,试着控制某些高级抽象的语言内部接口。比如,黑客喜欢破解,而破解就意味着深入内部,揣测原始设计者的意图。
你应该敞开胸怀,欢迎这种揣测,对于制造工具的人来说,总是会有用户以违背你本意的方式使用你的工具。如果你制造的是编程语言这样高度组合的系统,那就更是如此了。许多黑客会用你做梦也想不到的方式改动你的语法模型。我的建议就是,让他们这样干吧,而且应该为他们创造便利,尽可能多地把语言的内部暴露在他们面前。
其实,黑客并不会彻底颠覆你的工具,在一个大型程序中,他可能只是对语言改造一两个地方。但是,改动多少地方并不重要,重要的是他能够对语言进行改动。这可能不仅有助于解决一些特殊的问题,还会让黑客觉得很好玩。黑客改造语言的乐趣就好比外科医生摆弄病人内脏的乐趣,或者青少年喜欢用手挤破青春痘的那种感觉。至少对男生来说,某些类型的破坏非常刺激。针对青年男性读者的Maxim杂志每年出版一本特辑,里面一半是美女照片,另一半是各种严重事故的现场照片。这本杂志非常清楚它的读者想看什么^。
^「在《神经外科医生手记》(When the Air Hits Your Brain)一书中,神经外科医生弗托塞克讲述了住院总医生戈雷的一段话,内容关于外科医生与内科医生的区别。
> 戈雷和我要了一个大比萨,找了一张空桌子坐下。他点起一根香烟,说:“那些内科医生真是令人讨厌,总是喜欢谈论一辈子只能遇到一次的病例。这就是他们的问题,他们只喜欢古怪的东西,讨厌普通的常见病例。这就是我们和他们的区别。你看,我们喜欢腰椎间盘突出,觉得像比萨一样又大又好吃,但是他们看到高血压就憎恨不已……”
很难把腰椎间盘突出与又大又好吃联系在一起,徂是,我想我知道他们指的是什么。我经常觉得某个bug非常诱人,一定要追踪下去。不是程序员的人很难想象bug有什么好玩的。一切正常当然很好,但是不可否认,能够抓到某些bug会让人兴奋到极点。」
一种真正优秀的编程语言应该既整洁又混乱。“整洁”的意思是设计得很清楚,内核由数量不多的运算符构成,这些运算符易于理解,每一个都有很完整的独立用途。“混乱”的意思是它允许黑客以自己的方式使用。C语言就是这样的例子,早期的Lisp语言也是如此。真正的黑客语言总是稍微带一点放纵不羁、不服管敎的个性。
优秀的编程语言所具备的功能,应该会使得言必称“软件工程”的人感到非常不满、频频摇头。与黑客语言形成鲜明对照的就是像Pascal那样的语言,它是井然有序的模范,非常适合教学,但是除此之外就没有很大用处了。
### 一次性程序
为了吸引黑客,一种编程语言必须善于完成黑客想要完成的各种任务。这意味着它必须很适合开发一次性程序。这一点可能出乎很多人的意料。
所谓一次性程序,就是指为了完成某些很简单的临时性任务而在很短时间内写出来的程序。比如,自动完成某些系统管理任务的程序,或者(为了某项模拟任务)自动生成测试数据的程序,以及在不同格式之间转化数据的程序等。令人吃惊的是,一次性程序往往不是真的只用一次,就像二战期间很多美国大学造的一大批临时建筑后来都成了永久建筑。许多一次性程序后来也都变成了正式的程序,具备了正式的功能和外部用户。
我有一种预感,最优秀的那些大型程序就是这样发展起来的,而不是像胡佛水坝那样从一开始就作为大型工裎来设计。一下子从无到有做出一个大项目是很恐怖的一件事。当人们接手一个巨型项目时,很容易被它搞得一蹶不振。最后,要么是项目陷入僵局,要么是做出来一个规模小、性能差的东西。你想造一片闹市,却只做出一家商场;你想建一个罗马,却只造出一个巴西利亚;你想发明C语言,却只开发出Ada。
开发大型程序的另一个方法就是从一次性程序开始,然后不断地改进。这种方法比较不会让人望而生畏,程序在不断的开发之中逐渐进步。一般来说,使用这种方法开发程序,一开始用什么编程语言,就会一直用到最后,因为除非有外部政治因素的干预,程序员很少会中途更换编程语言。所以,我们就有了一个看似矛盾的结论:如果你想设计一种适合开发大型项目的编程语言,就必须使得这种语言也适合开发一次性程序,因为大型项目就是从一次性程序演变而来的。
Perl就是一个鲜明的例子。它不仅仅设计成适合开发一次性程序,而且它本身就很像一次性程序。最初的Perl只是好几个生成表格的工具收集在一起而已。后来程序员用它写一次性程序,当那些程序逐渐发展壮大后,Perl才随之发展成了一种正式的编程语言。到了Perl 5,这种语言才适合开发重要的程序,但是在此之前它已经广为流行了。
什么样的语言适合写一次性程序?首先,它必须很容易装备。一次性程序是你只想在一小时内写出来的程序,所以它不应该耗费很多时间安装和配置,最好已经安装在你的电脑上了。它必须是想用就用的。C语言可以想用就用,因为它是操作系统的一部分;Perl可以想用就用,因为它本来就是一种系统管理工具,操作系统已经默认安装它了。
很容易装备不仅仅指很容易安装或者已经安装,还指很容易与使用者互动。一种有命令行界面、可以实时反馈的语言就具有互动性,那些必须先编译后使用的语言就不具备互动性。受欢迎的编程语言应该是前者,具有良好的互动性,可以快速得到运行结果。
一次性程序的另一个特点就是简洁。对黑客来说,这一点永远有吸引力。如果考虑到你最多只打算在这个程序上耗费一个小时,这一点就更重要了。
### 函数库
简洁性的最高形式当然是有人已经帮你把程序写好,你只要运行就可以了。函数库就是别人帮你写好的程序,所以它是编程语言的另一个重要特点,并且我认为正在变得越来越重要。Perl就赢在它具有操作字符串的巨大函数库。这类函数库对一次性程序特别重要,因为开发一次性程序的原始目的往往就是转化或提取字符串。许多Perl程序的原型可能就是把几个函数库调用放在一起。
我认为,未来50年中,编程语言的进步很大一部分与函数库有关。未来的函数库将像语言内核一样精心设计。优秀函数库的重要性将超过语言本身。某种语言到底是静态类型还是动态类型、是面向对象还是函数式编程,这些都不如函数库重要。那些习惯用变量类型考虑问题的语言设计者可能会对这种趋势感到不寒而栗。这不等于把语言设计降到开发应用程序的层次吗?哦,真是太糟了。但是别忘了,编程语言是供程序员使用的,而函数库就是程序员需要的东西。
设计优秀的函数库是很难的,并不只是写一大堆代码而已。一且函数库数量变得太多,找到一个你需要的函数有时候还不如自己动手写来得快。函数库的设计基础与语言内核一样,都是一个小规模的正交运算符集合。函数库的使用应该符合程序员的直觉,让他可以猜得出哪个函数能满足自己的需要。
### 效率
众所周知,好的编程语言生成的代码有较快的运行速度。但是实际上,我觉得代码的运行速度不是编程语言的设计者能够控制的。高德纳很久以前就指出,运行速度只取决于一些关键的瓶颈。而在编程实践中,许多程序员都已经注意到自己很容易搞错瓶颈到底在哪里。
所以,编程时提高代码运行速度的关键是使用好的性能分析器(profiler),而不是使用其他方法,比如精心选择一种静态类型的编程语言。为了提高运行速度,并没有必要每个函数的每个参数类型都声明清楚,你只需要在瓶颈处声明清楚参数类型就可以了。所以,更重要的是你需要能够找出瓶颈到底在什么地方。
人们在使用非常高级的语言(比如Lisp)时,经常抱怨很难知道哪个部分对性能的影响比较大。可能确实如此,如果你使用一种非常抽象的语言,这也许是无法避免的。不管怎样,我认为一个好的性能分析器会解决这个问题,虽然这方面还有很长的路要走,但是未来你可以快速知道程序每个部分的时间开销。
这个问题一部分源于沟通不畅。语言设计者喜欢提高编译器的速度,认为这是对自己技术水平的考验,而最多只把性能分析器当作一个附送给使用者的赠品。但是在现实中,一个好的性能分析器对程序的帮助可能大于编译器的作用。这里又一次反映出语言设计者与用户之间发生了脱节,前者竭尽全力想要解决的问题其实方向不甚正确。
让性能分析器自动运行可能是一个好主意。它自动告诉程序员每个部分的性能,而不是非要等到程序员手动运行后才能知道。比如,当程序员编辑源码的时候,代码编辑器能够实时用红色显示瓶颈的部分。另一个方法应该是设法显示正在运行的程序的情况,这对互联网软件尤其重要,因为服务器上有很多程序同时运行,它们都需要你密切关注。自动运行的性能分析器用图形实时显示程序运行时的内存状况,甚至可以发出声音,表示出现了问题。
出现问题时,声音是很好的提示。我们在Viaweb搞了一块很大的面板,上面有各种各样的仪表盘,用来显示服务器的状况。仪表盘的指针由微型马达驱动,每当马达旋转的时候,就会发出一阵轻微的噪音。在我的工位没法看到仪表盘,但是只要我听到声音,就能立刻知道服务器出现了问题。
性能分析器甚至有可能自动找出不合理的算法。如果将来有人发现某种形式的内存访问是不合理算法的信号,我不会感到很惊讶。如果有一个小人儿可以钻进计算机看看我们的程序是怎么运行的,他可能会变成一个忙碌又悲惨的可怜虫,就像那些为政府跑腿的小人物。我总觉得自己用处理器做了很多无用功,伹是一直没有找到能够看出程序是怎样浪费运算能力的好办法。
现在有一些语言先编译成字节码(byte code),然后再由解释器执行。这样做主要是为了让代码容易移植到不同的操作系统,伹是这也可以变成一项很有用的功能。让字节码成为语言的正式组成部分,允许程序员在瓶颈处内嵌字节码,这可能是一个不错的主意。然后,针对这部分字节码的优化也就变得可以移植了。
正如许多最终用户已经意识到的,运行速度的概念正在发生变化。随着互联网软件的兴起,越来越多的程序主要不是受限于计算机的运算速度,而是受限于I/O的速度。加快I/O速度将是很值得做的一件事。在这方面,编程语言也能起到作用,有些措施是显而易见的,比如采用简洁、快速、格式化输出的函数,还有些措施则需要深层次的结构变化,比如采用缓存和持久化对象(persistent object)。
用户关心的是反应时间(response time),但是软件的另一种效率正在变得越来越重要,那就是每个处理器能够同时支持的用户数量。未来许多有趣的应用程序都将是运行在服务器端的互联网软件,所以每台服务器能够支持的用户数量就成了软件业者的关键问题。互联网软件的资本支出就取决于这个指标。
许多年以来,大多数面向最终用户的程序都不太关心效率。软件开发者总是假设用户桌面电脑的运算能力会不断增长,所以不用刻意提高软件的效率。帕金森定律^被证明与摩尔定律一样颠扑不破。软件不断膨胀,消耗光所有可以得到的资源。这一切将随着互联网软件的出现发生改变,因为硬件和软件现在捆绑在一起供应。对于那些提供互联网软件的公司来说,每台服务器支持的用户数量最大化会对降低成本产生巨大影响。
^「帕金森定律(Parkinson's Law)的一种原始表达形式是“工作总是到最后一刻才会完成”,后来引申到计算机领域就变成了“数据总是会填满所有空间”,更一般性的总结则是“对一种资源的需求总是会消耗光这种资源的所有供应”。——译者注」
在一些应用程序中,处理器的运算能力是瓶颈,那么最重要的优化对象就是软件的运行速度。但是,一般情况下内存才是瓶颈,你能够同时支持的用户数量取决于用户数据所消耗的内存。编程语言在这方面也能发挥作用,对线程的良好支持将使得所有用户共享同一个内存堆(heap)。持久化对象和语言内核级别的延迟加载(lazy loading)支持也有助于减少内存需求。
### 时间
一种编程语言要想变得流行,最后一关就是要经受住时间的考验。没人想用一种会被淘汰的语言编程,这方面已经有很多前车之鉴了。所以,大多数黑客往往会等上几年,看看某一种新语言的势头,然后才真正考虑使用它。
新事物的发明者通常对这个发现很震惊,他们没想到人们居然这样对待发明创造。但是,让别人相信一种新事物是需要时间的。我有一个朋友,他的客户第一次提出某种需求时,他很少理会。因为他知道人们有时候会想要自己并不真正需要的东西。为了避免浪费时间,只有当客户第三次或第四次提出同样的需求时,他才认真对待。这个时候客户可能已经很不高兴了,但是这至少保证他们提出的需求应该就是他们真正需要的东西。
大多数人接触新事物时都学会了使用类似的过滤机制。甚至有时要听到别人提起十遍以上他们才会留意。这样做完全是合理的,因为大多数的热门新商品事后被证明都是浪费时间的噱头,没多久就消失得无影无踪。虚拟现实建模语言VRML刚诞生时曾经轰动一时,但是我决定等到一两年后再去学习它,结果一两年后已经没有学习的必要了,因为市场已经把它遗忘了。
所以,发明新事物的人必须有耐心,要常年累月不断地做市场推广,直到人们开始接受这种发明。我们就耗费了好几年才使得客户明白ViaWeb不需要下载安装就能使用。不过,好消息是,简单重复同一个信息就能解决这个问题。你只需要不停地重复同一句话,最终人们将会开始倾听。人们真正注意到你的时候,不是第一眼看到你站在那里,而是发现过了这么久你居然还在那里。
新事物的发展改进一般也需要很长时间。大多数技术在诞生后都逐渐发生了巨大的变化,编程语言更是如此。诞生头几年,一小批早期使用者比其他因素更能促进技术发展。早期使用者都是行家,要求也很高,能够很快找出你的技术中存在的缺点。而且,如果你的用户只有很少几个人,你就能够与他们所有人保持密切接触。只要不断改进你的系统,即使给用户造成了损失,早期使用者也会对你宽容大度的。
新技术被市场接纳的方式有两种,一种是自然成长式,另一种是大爆炸式。自然成长式的一个例子就是在车库里白手起家、自力更生的创业者。几个好朋友埋头工作,在外界毫不知晓的情况下开发出某种新技术。他们把它推向市场,没有任何宣传,最初的用户寥寥无几(但是热心程度无与伦比)。创业者持续改进新技术,与此同时,通过口碑效应,用户数量不断增长。在创业者不经意间,他们已经壮大起来了。
大爆炸式的例子是有风险资本支持、在市场上大张旗鼓宣传的创业公司。他们急急忙忙地开发一个产品,推向市场的时候大肆曝光,立刻就获得了一大批使用者(至少他们希望如此)。
一般来说,车库里的创业者会妒忌大爆炸式的创业公司。后者的主导人物个个光彩照人、自信非凡,深受风险资本商的追捧。他们什么都买得起,在公关公司配合产品推出的宣传活动中,他们自己也附带成为了明星人物。自然成长式的创业者坐在自家车库里,觉得自己又穷又可怜。伹是我想他们不必难过。最终来看,自然成长式会比大爆炸式产生更好的技术,能为创始人带来更多的财富。如果你研究一下目前的主流技术,就会发现大部分都是源于自然成长式。
这种模式不仅存在于商业公司,还存在于科研活动中。Multics操作系统和Ada语言是大爆炸式项目,现在都已经销声匿迹了,而它们的继承者Unix和C语言则是自然成长式项目。
### 再设计
著名散文家E.B.怀特说过,“最好的文字来自不停的修改”。所有优秀作家都知道这一点,它对软件开发也适用。设计一样东西,最重要的一点就是要经常“再设计”,编程尤其如此,再多的修改都不过分。
为了写出优秀软件,你必须同时具备两种互相冲突的信念。一方面,你要像初生牛犊一样,对自己的能力信心万丈;另一方面,你又要像历经沧桑的老人一样,对自己的能力抱着怀疑态度。在你的大脑中,有一个声音说“千难万险只等闲”,还有一个声音却说“早岁哪知世事艰”。
这里的难点在于你要意识到,实际上这两种信念并不矛盾。你的乐观主义和怀疑倾向分别针对两个不同的对象。你必须对解决难题的可能性保持乐观,同时对当前解法的合理性保持怀疑。
做出优秀成果的人,在做的过程中常常觉得自己做得不够好。其他人看到他们的成果觉得棒极了,而创造者本人看到的都是自己作品的缺陷。这种视角的差异并非偶然,因为只有对现状不满,才会造就杰出的成果。
如果你能平衡好希望和担忧,它们就会推动项目前进,就像自行车在保持平衡中前进一样。在创新活动的第一阶段,你不知疲倦地猛攻某个难题,自信一定能够解决它。到了第二阶段,你在清晨的寒风中看到自己已经完成的部分,清楚地意识到存在各种各样的缺陷。此时,只要你对自己的怀疑没有超过你对自己的信心,就能够坦然接受这个半成品,心想不管多难我还是可以把剩下的部分做完。
让这两股相反的力量保持平衡是很难的。初出茅庐的年轻黑客都很乐观,自以为做出了伟大的产品,从不反思和改进。上了年纪的黑客又太不自信,甚至故意回避一些挑战性很强的项目。
任何措施,只要能让“再设计”周而复始地进行下去,就都是可取的。文章可以修改到你满意为止,但是软件的修改通常来说可以无休止地进行下去。文章的读者不可能抱怨修改后新增加的内容让他们前后的思想产生了不协调,但是软件的使用者就会抱怨修改后的版本有不兼容问题。
用户是一把双刃剑。他们推动语言的发展,但也使得你不敢对语言进行大规模改造。所以,一开始的时候要精心选择用户,避免使用者过快增长。发展用户就像一种优化过程,明智的做法就是放慢速度。一般情况下,用户比较少意味着你任何时候都可以加大修改的力度。这时,对语言规格做出改变就像撕绷带,当你感到痛苦的一瞬间,痛苦就已经成为了回忆。如果用户数量庞大,修改语言带来的痛苦就将持续很长时间。
大家都知道,让一个委员会负责设计语言是非常糟糕的主意。委员会只会做出恶劣的设计。但是我觉得,委员会最大的问题在于他们妨碍了“再设计”。在委员会的主持下,修改一种语言是非常麻烦的事,没有人愿意自讨苦吃。而且,即使大多数成员不喜欢某种做法,委员会最后的决定往往还是维持现状。
就算委员会只有两个人,还是会妨碍“再设计”,典型例子就是软件内部的各个接口由不同的人负责。这时除非两个人都同意改变接口,否则接口就无法改变。因此现实中,尽管软件功能越来越强大,内部接口却往往一成不变,成为整个系统中拖后腿的部分。
一种可能的解决方法是,将软件内部的接口设计成垂直接口而不是水平接口。这意味着软件内部的模块是一个个垂直堆积起来的抽象层,层与层之间的接口完全由其中的一层控制。如果较高的一层使用了较低的一层定义的语言,那么接口就由较低的一层控制;如果较低的一层从属于较高的一层,那么接口就由较高的一层控制。
### 梦寐以求的编程语言
让我们试着描述黑客心目中梦寐以求的语言来为以上内容做个小结。这种语言干净简练,具有最高层次的抽象和互动性,而且很容易装备,可以只用很少的代码就解决常见的问题。不管是什么程序,你真正要写的代码几乎都与你自己的特定设置有关,其他具有普遍性的问题都有现成的函数库可以调用。
这种语言的句法短到令人生疑。你输入的命令中,没有任何一个字母是多余的,甚至用到Shift键的杌会也很少。
这种语言的抽象程度很高,使得你可以快速写出一个程序的原型。然后,等到你开始优化的时候,它还提供一个真正出色的性能分析器,告诉你应该重点关注什么地方。你能让多重循环快得难以置信,并且在需要的地方还能直接嵌入字节码。
这种语言有大量优秀的范例可供学习,而且非常符合直觉,你只需花几分钟阅读范例就能领会应该如何使用此种语言。你偶尔才需要查阅操作手册,它本身很薄,里面关于限定条件和例外情况的警告寥寥无几。这种语言的内核很小,但很强大。各个函数库高度独立,而且和内核一样经过精心设计,它们都能很好地协同工作。语言的每个部分就像精密照相机的各种零件一样完美契合,不需要为了兼容性问题放弃或者保留某些功能。所有函数库的源码都很容易得到。这种语言能够很轻松地与操作系统和用其他语言开发的应用裎序对话。
这种语言以层的方式构建。较高的抽象层透明地构建在较低的抽象层之上。如果需要的话,你可以直接使用较低的抽象层。
除了一些绝对必要隐藏的东西,这种语言的所有细节对使用者都是透明的。它提供的抽象能力只是为了方便你的开发,而不是为了强迫你按照它的方式行事。事实上,它鼓励你参与它的设计,给你提供与语言创造者平等的权力。你能够对它的任何部分加以改变,甚至包括它的语法。它尽可能让你自己定义的部分与它本身定义的部分处于同等地位。这种梦幻般的编程语言不仅开放源码,更开放自身的设计。