Unix基本哲学
-----------------------------------------------------------------------------------------
--------------------------------------------------中国 IT实验室整理 2009-8-26
Unix哲学起源于Ken Thompson早期关于如何设计一个服务接口
简洁、小巧精干的操作系统的思考,随着 Unix 文化在学习如何尽可
能发掘 Thompson设计思想的过程中不断成长,同时一路上还从其它
许多地方博采众长。
Unix 哲学说来不算是一种正规设计方法。它并不打算从计算机
科学的理论高度来产生理论上完美的软件。那些毫无动力、松松垮垮
而且薪水微薄的程序员们,能在短短期限内,如同神灵附体般造出稳
定而新颖的软件——这只不过是经理人永远的梦呓罢了。
Unix 哲学(同其它工程领域的民间传统一样)是自下而上的,
而不是自上而下的。Unix 哲学注重实效,立足于丰富的经验。你不
会在正规方法学和标准中找到它,它更接近于隐性的半本能的知识,
即 Unix 文化所传播的专业经验。它鼓励那种分清轻重缓急的感觉,
以及怀疑一切的态度,并鼓励你以幽默达观的态度对待这些。
Unix 管道的发明人、Unix 传统的奠基人之一 Doug McIlroy 在
[McIlroy78]中曾经说过:
(i)让每个程序就做好一件事。如果有新任务,就重新开始,
不要往原程序中加入新功能而搞得复杂。
(ii)假定每个程序的输出都会成为另一个程序的输入,哪怕那
个程序还是未知的。输出中不要有无关的信息干扰。避免使用严格的
分栏格式和二进制格式输入。不要坚持使用交互式输入。
(ⅲ)尽可能早地将设计和编译的软件投入试用, 哪怕是操作系
统也不例外,理想情况下, 应该是在几星期内。对拙劣的代码别犹豫,
扔掉重写。
(iv)优先使用工具而不是拙劣的帮助来减轻编程任务的负担。
工欲善其事,必先利其器。
后来他这样
道(引自《Unix 的四分之一世纪》(A Quarter
Century of Unix [Salus])):
Unix 哲学是这样的:一个程序只做一件事,并做好。程序要能
协作。程序要能处理文本流,因为这是最通用的接口。
Rob Pike, 最伟大的 C 语言大师之一 , 在《Notes on C
Programming》中从另一个稍微不同的角度
述了 Unix的哲学[Pike]:
原则 1:你无法断定程序会在什么地方耗费运行时间。瓶颈经常
出现在想不到的地方,所以别急于胡乱找个地方改代码,除非你已经
证实那儿就是瓶颈所在。
原则 2:估量。在你没对代码进行估量,特别是没找到最耗时的
那部分之前,别去优化速度。
原则 3:花哨的算法在 n很小时通常很慢,而 n通常很小。花哨
算法的常数复杂度很大。除非你确定 n总是很大,否则不要用花哨算
法(即使 n很大,也优先考虑原则 2)。
原则 4:花哨的算法比简单算法更容易出 bug、更难实现。尽量
使用简单的算法配合简单的数据结构。
原则 5:数据压倒一切。如果已经选择了正确的数据结构并且把
一切都组织得井井有条,正确的算法也就不言自明。编程的核心是数
据结构,而不是算法。
原则 6:没有原则 6。
Ken Thompson——Unix最初版本的设计者和实现者,禅宗偈语
般地对 Pike的原则 4作了强调:拿不准就穷举。
Unix 哲学中更多的内容不是这些先哲们口头表述出来的,而是
由他们所作的一切和 Unix 本身所作出的榜样体现出来的。从整体上
来说,可以概括为以下几点:
1. 模块原则:使用简洁的接口拼合简单的部件。
2. 清晰原则:清晰胜于机巧。
3. 组合原则:设计时考虑拼接组合。
4. 分离原则:策略同机制分离,接口同引擎分离。
5. 简洁原则:设计要简洁,复杂度能低则低。
6. 吝啬原则:除非确无它法,不要编写庞大的程序。
7. 透明性原则:设计要可见,以便审查和调试。
8. 健壮原则:健壮源于透明与简洁。
9. 表示原则:把知识叠入数据以求逻辑质朴而健壮。
10. 通俗原则:接口设计避免标新立异。
11. 缄默原则:如果一个程序没什么好说的,就沉默。
12. 补救原则:出现异常时,马上退出并给出足够错误信息。
13. 经济原则:宁花机器一分,不花程序员一秒。
14. 生成原则:避免手工 hack,尽量编写程序去生成程序。
15. 优化原则:雕琢前先要有原型,跑之前先学会走。
16. 多样原则:决不相信所谓“不二法门”的断言。
17. 扩展原则:设计着眼未来,未来总比预想来得快。
如果刚开始接触 Unix,这些原则值得好好体味一番。谈软件工
程的文章常常会推荐大部分的这些原则,但是大多数其它操作系统缺
乏恰当的工具和传统将这些准则付诸实践,所以,多数的程序员还不
能自始至终地贯彻这些原则。蹩脚的工具、糟糕的设计、过度的劳作
和臃肿的代码对他们已经是家常便饭了;他们奇怪,Unix 的玩家有
什么好烦的呢。
1.6.1 模块原则:使用简洁的接口拼合简单的部件
正如 Brian Kernighan曾经说过的:“计算机编程的本质就是控制
复杂度”[Kernighan-Plauger]。排错占用了大部分的开发时间,弄出
一个拿得出手的可用系统,通常与其说出自才华横溢的设计成果,还
不如说是跌跌撞撞的结果。
汇编语言、编译语言、
图、过程化编程、结构化编程、所谓
的人工智能、第四代编程语言、面向对象、以及软件开发的方法论,
不计其数的解决之道被抛售者吹得神乎其神。但实际上这些都用处不
大,原因恰恰在于它们“成功”地将程序的复杂度提升到了人脑几乎
不能处理的地步。就像 FredBrooks的一句名言[Brooks]:没有万能药。
要编制复杂软件而又不至于一败涂地的唯一方法就是降低其整
体复杂度——用清晰的接口把若干简单的模块组合成一个复杂软件。
如此一来,多数问题只会局限于某个局部,那么就还有希望对局部进
行改进而不至牵动全身。
1.6.2 清晰原则: 清晰胜于机巧
维护如此重要而成本如此高昂;在写程序时,要想到你不是写给
执行代码的计算机看的,而是给人——将来阅读维护源码的人,包括
你自己——看的。
在 Unix传统中,这个建议不仅意味着代码注释。良好的 Unix实
践同样信奉在选择算法和实现时就应该考虑到将来的可扩展性。而为
了取得程序一丁点的性能提升就大幅度增加技术的复杂性和晦涩性,
这个买卖做不得——这不仅仅是因为复杂的代码容易滋生 bug,也因
为它会使日后的阅读和维护工作更加艰难。
相反,优雅而清晰的代码不仅不容易崩溃——而且更易于让后来
的修改者立刻理解。这点非常重要,尤其是说不定若干年后回过头来
修改这些代码的人可能恰恰就是你自己。
永远不要去吃力地解读一段晦涩的代码三次。第一次也许侥幸成
功,但如果发现必须重新解读一遍——离第一次太久了,具体细节无
从回想——那么你该注释代码了,这样第三次就相对不会那么痛苦
了。—Henry Spencer
1.6.3 组合原则:设计时考虑拼接组合
如果程序彼此之间不能有效通信,那么软件就难免会陷入复杂度
的泥淖。
在输入输出方面,Unix 传统极力提倡采用简单、文本化、面向
流、设备无关的格式。在经典的 Unix 下,多数程序都尽可能采用简
单过滤器的形式,即将一个输入的简单文本流处理为一个简单的文本
流输出。
抛开世俗眼光,Unix 程序员偏爱这种做法并不是因为他们仇视
图形用户界面,而是因为如果程序不采用简单的文本输入输出流,它
们就极难衔接。
Unix 中,文本流之于工具,就如同在面向对象环境中的消息之
于对象。文本流界面的简洁性加强了工具的封装性。而许多精致的进
程间通讯方法,比如远程过程调用,都存在牵扯过多各程序间内部状
态的倾向。
要想让程序具有组合性,就要使程序彼此独立。在文本流这一端
的程序应该尽可能不要考虑文本流另一端的程序。将一端的程序替换
为另一个截然不同的程序,而完全不惊扰另一端应该很容易做到。
GUI可以是个好东西。有时竭尽所能也不可避免复杂的二进制数
据格式。但是,在做一个 GUI 前,最好还是应该想想可不可以把复
杂的交互程序跟干粗活的算法程序分离开,每个部分单独成为一块,
然后用一个简单的命令流或者是应用
将其组合在一起。
在构思精巧的数据传输格式前,有必要实地考察一下,是否能利
用简单的文本数据格式;以一点点格式解析的代价,换得可以使用通
用工具来构造或解读数据流的好处是值得的。
当程序无法自然地使用序列化、协议形式的接口时,正确的 Unix
设计至少是,把尽可能多的编程元素组织为一套定义良好的 API。这
样,至少你可以通过链接调用应用程序,或者可以根据不同任务的需
求粘合使用不同的接口。(我们将在第 7章详细讨论这些问题。)
1.6.4 分离原则: 策略同机制分离,接口同引擎分离
在 Unix之失的讨论中,我们谈到过 X系统的设计者在设计中的
基本抉择是实行“机制,而不是策略”这种做法——使 X 成为一个
通用图形引擎,而将用户界面风格留给工具包或者系统的其它层次来
决定。这一点得以证明是正确的,因为策略和机制是按照不同的时间
尺度变化的,策略的变化要远远快于机制。GUI工具包的观感时尚来
去匆匆,而光栅操作和组合却是永恒的。
所以,把策略同机制揉成一团有两个负面影响:一来会使策略变
得死板,难以适应用户需求的改变,二来也意味着任何策略的改变都
极有可能动摇机制。
相反,将两者剥离,就有可能在探索新策略的时候不足以打破机
制。另外,我们也可以更容易为机制写出较好的测试(因为策略太短
命,不值得花太多精力在这上面)。
这条设计准则在 GUI 环境之外也被广泛应用。总而言之,这条
准则告诉我们应该设法将接口和引擎剥离开来。
实现这种剥离的一个方法是,比如,将应用按照一个库来编写,
这个库包含许多由内嵌脚本语言驱动的 C 服务程序,而至于整个应
用的控制流程则用脚本来撰写而不是用 C 语言。这种模式的经典例
子就是 Emacs 编辑器,它使用内嵌的脚本语言 Lisp 解释器来控制用
C编写的编辑原语操作。我们会在第 11章讨论这种设计风格。
另一个方法是将应用程序分成可以协作的前端和后端进程,通过
套接字上层的专用应用协议进行通讯;我们会在第 5章和第 7章讨论
这种设计。前端实现策略,后端实现机制。比起仅用单个进程的整体
实现方式来说,这种双端设计方式大大降低了整体复杂度,bug有望
减少,从而降低程序的寿命周期成本。
1.6.5 简洁原则:设计要简洁,复杂度能低则低
来自多方面的压力常常会让程序变得复杂(由此代价更高,bug
更多),其中一种压力就是来自技术上的虚荣心理。程序员们都很聪
明,常常以能玩转复杂东西和耍弄抽象概念的能力为傲,这一点也无
可厚非。但正因如此,他们常常会与同行们比试,看看谁能够鼓捣出
最错综复杂的美妙事物。正如我们经常所见,他们的设计能力大大超
出他们的实现和排错能力,结果便是代价高昂的废品。
“错综复杂的美妙事物”听来自相矛盾。Unix 程序员相互比的
是谁能够做到“简洁而漂亮”并以此为荣,这一点虽然只是隐含在这
些规则之中,但还是很值得公开提出来强调一下。
更为常见的是(至少在商业软件领域里),过度的复杂性往往来自
于项目的要求,而这些要求常常基于当月的推销热点,而不是基于顾
客的需求和软件实际能够提供的功能。许多优秀的设计被市场推销所
需要的大堆大堆“特性清单”扼杀——实际上,这些特性功能几乎从
未用过。然后,恶性循环开始了:比别人花哨的方法就是把自己变得
更花哨。很快,庞大臃肿变成了业界标准,每个人都在使用臃肿不堪、
bug极多的软件,连软件开发人员也不敢敝帚自珍。
无论以上哪种方式,最后每个人都是失败者。
要避免这些陷阱,唯一的方法就是鼓励另一种软件文化,以简洁
为美,人人对庞大复杂的东西群起而攻之——这是一个非常看重简单
解决方案的工程传统,总是设法将程序系统分解为几个能够协作的小
部分,并本能地抵制任何用过多噱头来粉饰程序的企图。
这就有点 Unix文化的意味了。
1.6.6 吝啬原则: 除非确无它法,不要编写庞大的程序
“大”有两重含义:体积大,复杂程度高。程序大了,维护起来
就困难。由于人们对花费了大量精力才做出来的东西难以割舍,结果
导致在庞大的程序中把投资浪费在注定要失败或者并非最佳的方案
上。(我们会在第 13章就软件的最佳大小进行更多的详细讨论。)
1.6.7 透明性原则:设计要可见,以便审查和调试
因为调试通常会占用四分之三甚至更多的开发时间,所以一开始
就多做点工作以减少日后调试的工作量会很划算。一个特别有效的减
少调试工作量的方法就是设计时充分考虑透明性和显见性。
软件系统的透明性是指你一眼就能够看出软件是在做什么以及
怎样做的。显见性指程序带有监视和显示内部状态的功能,这样程序
不仅能够运行良好,而且还可以看得出它以何种方式运行。
设计时如果充分考虑到这些要求会给整个项目全过程都带来好
处。至少,调试选项的设置应该尽量不要在事后,而应该在设计之初
便考虑进去。这是考虑到程序不但应该能够展示其正确性,也应该能
够把原开发者解决问题的思维模型告诉后来者。
程序如果要展示其正确性,应该使用足够简单的输入输出格式,
这样才能保证很容易地检验有效输入和正确输出之间的关系是否正
确。
出于充分考虑透明性和显见性的目的,还应该提倡接口简洁,以
方便其它程序对其进行操作——尤其是测试监视工具和调试脚本。
1.6.8 健壮原则: 健壮源于透明与简洁
软件的健壮性指软件不仅能在正常情况下运行良好,而且在超出
设计者设想的意外条件下也能够运行良好。
大多数软件禁不起磕碰,毛病很多,就是因为过于复杂,很难通
盘考虑。如果不能够正确理解一个程序的逻辑,就不能确信其是否正
确,也就不能在出错的时候修复它。
这也就带来了让程序健壮的方法,就是让程序的内部逻辑更易于
理解。要做到这一点主要有两种方法:透明化和简洁化。
就健壮性而言,设计时要考虑到能承受极端大量的输入,这一点
也很重要。这时牢记组合原则会很有益处;经不起其它一些程序产生
的输入(例如,原始的 Unix C编译器据说需要一些小小的升级才能
处理好 Yacc的输出)。当然,这其中涉及的一些形式对人类来说往往
看起来没什么实际用处。比如,接受空的列表、字符串等等,即使在
人们很少或者根本就不提供空字符串的地方也得如此,这可以避免在
用机器生成输入时需要对这种情况进行特殊处理。
在有异常输入的情况下,保证软件健壮性的一个相当重要的策略
就是避免在代码中出现特例。bug通常隐藏在处理特例的代码以及处
理不同特殊情况的交互操作部分的代码中。
上面我们曾说过,软件的透明性就是指一眼就能够看出来是怎么
回事。如果“怎么回事”不算复杂,即人们不需要绞尽脑汁就能够推
断出所有可能的情况,那么这个程序就是简洁的。程序越简洁,越透
明,也就越健壮.模块性(代码简朴,接口简洁)是组织程序以达到
更简洁目的的一个方法。另外也有其它的方法可以得到简洁。接下来
就是另一个。
1.6.9 表示原则: 把知识叠入数据以求逻辑质朴而健壮
即使最简单的程序逻辑让人类来验证也很困难,但是就算是很复
杂的数据,对人类来说,还是相对容易地就能够推导和建模的。不信
可以试试比较一下,是五十个节点的指针树,还是五十行代码的流程
图更清楚明了;或者,比较一下究竟用一个数组初始化器来表示转换
表,还是用 switch语句更清楚明了呢?可以看出,不同的方式在透明
性和清晰性方面具有非常显著的差别。参见 Rob Pike的原则 5。
数据要比编程逻辑更容易驾驭。所以接下来,如果要在复杂数据
和复杂代码中选择一个,宁愿选择前者。更进一步:在设计中,你应
该主动将代码的复杂度转移到数据之中去。
此种考量并非 Unix社区的原创,但是许多 Unix代码都显示受其
影响。特别是 C 语言对指针使用控制的功能,促进了在内核以上各
个编码层面上对动态修改引用结构。在结构中用非常简单的指针操作
就能够完成的任务,在其它语言中,往往不得不用更复杂的过程才能
完成。(我们将在第 9章再讨论这些技术。)
1.6.10 通俗原则:接口设计避免标新立异
(也就是众所周知的“最少惊奇原则”。)
最易用的程序就是用户需要学习新东西最少的程序——或者,换
句话说,最易用的程序就是最切合用户已有知识的程序。
因此,接口设计应该避免毫无来由的标新立异和自作聪明。如果
你编制一个计算器程序,‘+’应该永远表示加法。而设计接口的时
候,尽量按照用户最可能熟悉的同样功能接口和相似应用程序来进行
建模。
关注目标受众。他们也许是最终用户,也许是其他程序员,也许
是系统管理员。对于这些不同的人群,最少惊奇的意义也不同。
关注传统惯例。Unix 世界形成了一套系统的惯例,比如配置和
运行控制文件的格式,命令行开关等等。这些惯例的存在有个极好的
理由:缓和学习曲线。应该学会并使用这些惯例。(我们将在第 5 章
和第 10章讨论这些传统惯例。)
最小立异原则的另一面是避免表象相似而实际却略有不同。这会
极端危险,因为表象相似往往导致人们产生错误的假定。所以最好让
不同事物有明显区别,而不要看起来几乎一模一样。
1.6.11 缄默原则:如果一个程序没什么好说的,就保持沉默
Unix 中最古老最持久的设计原则之一就是:若程序没有什么特
别之处可讲,就保持沉默。行为良好的程序应该默默工作,决不唠唠
叨叨,碍手碍脚。沉默是金。
“沉默是金”这个原则的起始是源于 Unix 诞生时还没有视频显
示器。在 1969 年的缓慢的打印终端,每一行多余的输出都会严重消
耗用户的宝贵时间。现在,这种情况已不复存在,一切从简的这个优
良传统流传至今。
我认为简洁是 Unix 程序的核心风格。一旦程序的输出成为另一
个程序的输入,就很容易把需要的数据挑出来。站在人的角度上来
说――重要信息不应该混杂在冗长的程序内部行为信息中。如果显示
的信息都是重要的,那就不用找了。
设计良好的程序将用户的注意力视为有限的宝贵资源,只有在必
要时才要求使用。(我们将在第 11章末尾进一步讨论缄默原则及其理
由。)
1.6.12 补救原则: 出现异常时,马上退出并给出足量错误信息
软件在发生错误的时候也应该与在正常操作的情况下一样,有透
明的逻辑。最理想的情况当然是软件能够适应和应付非正常操作;而
如果补救措施明明没有成功,却悄无声息地埋下崩溃的隐患,直到很
久以后才显现出来,这就是最坏的一种情况。
因此,软件要尽可能从容地应付各种错误输入和自身的运行错
误。但是,如果做不到这一点,就让程序尽可能以一种容易诊断错误
的方式终止。
同时也请注意 Postel 的规定[8]:“宽容地收,谨慎地发”。Postel
谈的是网络服务程序,但是其含义可以广为适用。就算输入的数据很
不规范,一个设计良好的程序也会尽量领会其中的意义,以尽量与别
的程序协作;然后,要么响亮地倒塌,要么为工作链下一环的程序输
出一个严谨干净正确的数据。
然而,也请注意这条警告:最初 HTML 文档推荐“宽容地接受
数据”,结果因为每一种浏览器都只接受规范中一个不同的超集,使
我们一直倍感无奈。要宽容的应该是规范而不是它们的解释工具。
McIlroy 要求我们在设计时要考虑宽容性,而不是用过分纵容的
实现来补救标准的不足。否则,正如他所指出的一样,一不留神你会
死得很难看。
1.6.13 经济原则: 宁花机器一分,不花程序员一秒
在 Unix 早期的小型机时代,这一条观点还是相当激进的(那时
机器要比现在慢得多也贵得多)。如今,随着技术的发展,开发公司
和大多数用户(那些需要对核爆炸进行建模或处理三维电影动画的除
外)都能够得到廉价的机器,所以这一准则的合理性就显然不用多说
啦!
但不知何故,实践似乎还没完全跟上现实的步伐。如果我们在整
个软件开发中很严格的遵循这条原则的话,大多数的应用场合都应该
使用高一级的语言,如 Perl、Tcl、Python、Java、Lisp,甚至 shell—
—这些语言可以将程序员从自行管理内存的负担中解放出来(参见
[Ravenbrook])。
这种做法在 Unix世界中已经开始施行,尽管 Unix之外的大多数
软件商仍坚持采用旧 Unix学派的 C(或 C++)编码方法。本书会在后面
详细讨论这个策略及其利弊权衡。
另一个可以显著节约程序员时间的方法是:教会机器如何做更多
低层次的编程工作,这就引出了……
1.6.14 生成原则: 避免手工 hack,尽量编写程序去生成程序
众所周知,人类很不善于干辛苦的细节工作。因此,程序中的任
何手工 hacking都是滋生错误和延误的温床。程序规格越简单越抽象,
设计者就越容易做对。由程序生成代码几乎(在各个层次)总是比手写
代码廉价并且更值得信赖。
我们都知道确实如此(毕竟这就是为什么会有编译器、解释器的
原因),但我们却常常不去考虑其潜在的含义。对于代码生成器来说,
需要手写的重复而麻木的高级语言代码,与机器码一样是可以批量生
产的。当代码生成器能够提升抽象度时——即当生成器的说明性语句
要比生成码简单时,使用代码生成器会很合算,而生成代码后就根本
无需再费力地去手工处理了。
在 Unix 传统中,人们大量使用代码生成器使易于出错的细节工
作自动化。Parser/Lexer 生成器就是其中的经典例子,而 makefile 生
成器和 GUI界面式的构建器(interface builder)则是新一代的例子。
(我们会在第 9章讨论这些技术。)
1.6.15 优化原则: 雕琢前先得有原型,跑之前先学会走
原型设计最基本的原则最初来自于 Kernighan 和 Plauger 所说
的“90%的功能现在能实现,比 100%的功能永远实现不了强”。做好
原型设计可以帮助你避免为蝇头小利而投入过多的时间。
由于略微不同的一些原因,Donald Knuth(程序设计领域中屈指
可数的经典著作之一《计算机程序设计艺术》的作者)广为传播普及
了这样的观点:“过早优化是万恶之源”[9]。他是对的。
还不知道瓶颈所在就匆忙进行优化,这可能是唯一一个比乱加功
能更损害设计的错误。从畸形的代码到杂乱无章的数据布局,牺牲透
明性和简洁性而片面追求速度、内存或者磁盘使用的后果随处可见。
滋生无数 bug,耗费以百万计的人时——这点芝麻大的好处,远不能
抵消后续排错所付出的代价。
经常令人不安的是,过早的局部优化实际上会妨碍全局优化(从
而降低整体性能)。在整体设计中可以带来更多效益的修改常常会受
到一个过早局部优化的干扰,结果,出来的产品既性能低劣又代码过
于复杂。
在 Unix 世界里,有一个非常明确的悠久传统(例证之一是 Rob
Pike以上的评论, 另一个是 Ken Thompson关于穷举法的格言):先
制作原型,再精雕细琢。优化之前先确保能用。或者:先能走,再学
跑。“极限编程”宗师 Kent Beck 从另一种不同的文化将这一点有效
地扩展为:先求运行,再求正确,最后求快。
所有这些话的实质其实是一个意思:先给你的设计做个未优化
的、运行缓慢、很耗内存但是正确的实现,然后进行系统地调整,寻
找那些可以通过牺牲最小的局部简洁性而获得较大性能提升的地方。
制作原型对于系统设计和优化同样重要——比起阅读一个冗长
的规格说明,判断一个原型究竟是不是符合设想要容易得多。我记得
Bellcore有一位开发经理,他在人们还没有谈论“快速原型化”和“敏
捷开发”前好几年就反对所谓的“需求”文化。他从不提交冗长的规
格说明,而是把一些 shell脚本和 awk代码结合在一起,使其基本能
够完成所需要的任务,然后告诉客户派几个职员来使用这些原型,问
他们是否喜欢。如果喜欢,他就会说“在多少多少个月之后,花多少
多少的钱就可以获得一个商业版本”。他的估计往往很精确,但由于
当时的文化,他还是输给了那些相信需求分析应该主导一切的同行。
借助原型化找出哪些功能不必实现,有助于对性能进行优化;那
些不用写的代码显然无需优化。目前,最强大的优化工具恐怕就是
delete键了。
我最有成效的一天就是扔掉了 1000行代码。—Ken Thompson
(我们将在第 12章对相关内容进行深一步讨论。)
1.6.16 多样原则:决不相信所谓“不二法门”的断言
即使最出色的软件也常常会受限于设计者的想象力。没有人能聪
明到把所有东西都最优化,也不可能预想到软件所有可能的用途。设
计一个僵化、封闭、不愿与外界沟通的软件,简直就是一种病态的傲
慢。
因此, 对于软件设计和实现来说,Unix传统有一点很好,即从不
相信任何所谓的“不二法门”。Unix奉行的是广泛采用多种语言、开
放的可扩展系统和用户定制机制。
1.6.17 扩展原则: 设计着眼未来,未来总比预想快
如果说相信别人所宣称的“不二法门”是不明智的话,那么坚信
自己的设计是“不二法门”简直就是愚蠢了。决不要认为自己找到了
最终答案。因此,要为数据格式和代码留下扩展的空间,否则,就会
发现自己常常被原先的不明智选择捆住了手脚,因为你无法既要改变
它们又要维持对原来的兼容性。
设计协议或是文件格式时,应使其具有充分的自描述性以便可以
扩展。一直,总是,要么包含进一个版本号,要么采用独立、自描述
的语句,按照可以随时插入新的、换掉旧的而不会搞乱格式读取代码
的方法组织格式。Unix 经验告诉我们:稍微增加一点让数据部署具
有自描述性的开销,就可以在无需破坏整体的情况下进行扩展,你的
付出也就得到了成千倍的回报。
设计代码时,要有很好的组织,让将来的开发者增加新功能时无
需拆毁或重建整个架构。当然这个原则并不是说你能随意增加根本用
不上的功能,而是建议在编写代码时要考虑到将来的需要,使以后增
加功能比较容易。程序接合部要灵活 ,在代码中加入“如果你需
要……”的注释。有义务给之后使用和维护自己编写的代码的人做点
好事。
也许将来就是你自己来维护代码,而在最近项目的压力之下你很
可能把这些代码都遗忘了一半。所以,设计为将来着眼,节省的有可
能就是自己的精力。