程序员常常需要转换时间单位。比如说,一个程序每秒钟能处理 100 条记录,那它处理一百万条记录要多少时间?用除法一算,我们就知道要花 10000 秒,按每小时 3600 秒计算,差不多 3 个小时。

 

  然而一年又有多少秒呢?如果我直接告诉你,一共有 3.155 × 107 秒,你可能很快就忘了。事实上,要记住这个很简单,在误差不超过 0.5% 的约束下:

 

  π秒就是一个纳世纪。

 

  —— Tom Duff ,贝尔实验室

 

  所以,如果你的程序要运行 107 秒,你就要准备等上 4 个月。

 

   1985 年 2 月的《 ACM 通讯》曾向读者征集与计算有关的一句话箴言。读者来稿中有一些是没有争议的,比如 Duff 法则就是一种很方便的记忆常数的方法。而下面这个关于程序测试方法的法则中的数字则不那么绝对了(回归测试方法保存老版本的输入 / 输出数据,以确保新版本程序能得出同样的输出)。

 

  回归测试能将测试区间减半。

 

  —— Larry Bernstein ,贝尔通信研究院

 

   Bernstein 的观点中所说的数可能是 30% 也可能是 70% ,然而可以确定的是,这些测试节约了开发时间。

 

  不怎么定量的忠告也存在问题。相信大家都会同意

 

  小别胜新婚。

 

  ——佚名

 

  但也说

 

  眼不见,心不烦。

 

  ——佚名

 

  最后这句话对每个人都适用,对这些话本身则不适用。本章中的很多箴言也存在类似的矛盾。尽管每句话都有真理存焉,我们还是应该有所保留地看待它们。

 

  关于这些箴言的出处,我不得不声明一下。箴言下的名字基本上都是最早把这句话发给我的人,即使事实上这句话可能出自于他们的堂兄弟。在一些地方我列出了更早的参考文献以及作者的单位( 1985 年 9 月时的情况,那正是本章内容最初发表的时候)。我知道我这样做对不起那些最早说出这句话的人,我只能用下面这句话表达遗憾了:

 

  剽窃即是最诚恳的恭维。

 

  ——佚名

 

  闲话不说了,我直接把这些箴言分成几个大类,依次列出来。

 

   6.1  编码

 

  如果还没想清楚,就用蛮力算法吧。

 

  —— Ken Thompson ,贝尔实验室

 

  不要使用反正弦和反余弦函数——你总能用优美的恒等式,或者是计算向量点积来更好地解决这些问题。

 

  —— Jim Conyngham , Arvin/Calspan 高级技术中心

 

  在存储日期中的年份的时候,请使用四位数字:千禧年快要到了。

 

  —— David Martin ,宾夕法尼亚州诺里斯敦

 

  避免不对称结构。

 

  —— Andy Huber , Data General 公司

 

  代码写得越急,程序跑得越慢。

 

  —— Roy Carlson ,威斯康星大学

 

  你用英语都写不出来的东西就别指望用代码写了。

 

  —— Peter Halpern ,纽约州布鲁克林

 

  注意细节。

 

  —— Peter Weinberger ,贝尔实验室

 

  如果代码和注释不一致,那很可能两者都错了。

 

  —— Norm Schryer ,贝尔实验室

 

  如果你发现特殊情况太多,那你肯定是用错方法了。

 

  —— Craig Zerouni , Computer FX 公司(英国伦敦)

 

  先把数据结构搞清楚,程序的其余部分自现。

 

  —— David Jones ,荷兰阿森

 

   6.2  用户界面

 

  【最小惊异原则】尽可能让用户界面风格一致和可预测。

 

  ——几位读者提出

 

  计算机生成的输入通常会让一个原本设计接受手工输入的程序不堪重负。

 

  —— Dennis Ritchie ,贝尔实验室

 

  手工填写的表单中有 20% 都包含坏数据。

 

  —— Vic Vyssotsky ,贝尔实验室

 

   80% 的表单会要你回答没有必要的问题。

 

  —— Mike Garey ,贝尔实验室

 

  不要让用户提供那些系统已经知道的信息。

 

  —— Rick Lemons , Cardinal 数据系统公司

 

  所有数据集的 80% 中,有 95% 的信息量都可以用清晰的图表示。

 

  —— William S. Cleveland ,贝尔实验室

 

   6.3  调试

 

  在我所有的程序错误中, 80% 是语法错误。剩下的 20% 里, 80% 是简单的逻辑错误。在剩下的 4% 里, 80% 是指针错误。只有余下的 0.8% 才是困难的问题。

 

  —— Marc Donner , IBM 沃森研究中心

 

  在系统测试阶段找出并修正错误,要比开发者自己完成这一工作多付出 2 倍的努力。而当系统已经交付使用之后找出并修正一个错误,要比系统测试阶段多付出 9 倍的努力。因此,请坚持让开发者进行单元测试吧。

 

  —— Larry Bernstein ,贝尔通信研究院

 

  不要站着调试程序。那会使得你的耐心减半,你需要的是全神贯注。

 

  —— Dave Storer ,艾奥瓦州锡达拉皮兹

 

  别在注释里陷得太深——注释很可能会误导你的,你要调试的只是代码。

 

  —— Dave Storer ,艾奥瓦州锡达拉皮兹

 

  测试只能证明程序有错误,而不能证明程序没有错误。

 

  —— Edsger W. Dijkstra ,得克萨斯大学

 

  新系统的每一个新用户都可能发现一类新的错误。

 

  —— Brian Kernighan ,贝尔实验室

 

  东西没坏,就别乱修。

 

  ——罗纳德•里根,加州圣巴巴拉

 

  【维护者箴言】如果我们没能力修好它,我们就会告诉你它根本就没坏。

 

  —— Walt Weir ,美国陆军中校

 

  修正程序错误的第一步是要先重现这个错误。

 

  —— Tom Duff ,贝尔实验室

 

   6.4  性能

 

  【程序优化第一法则】不要优化。

 

  【程序优化第二法则——仅对专家适用】还是不要优化。

 

  —— Michael Jackson , Michael Jackson 系统公司

 

  对于那些快速算法,我们总是可以拿一些速度差不多但是更容易理解的算法来替代它们。

 

  —— Douglas W. Jones ,艾奥瓦大学

 

  在一些机器上,间接寻址比基址寻址要慢,所以请把结构体或记录中最常用的成员放在最前面。

 

  —— Mike Morton ,马萨诸塞州波士顿

 

  在一个非 I/O 密集型的程序中,超过一半的运行时间是花在不足 4% 的代码上的。

 

  —— Don Knuth ,斯坦福大学

 

  在优化一个程序之前,请先用性能监视工具找到程序的“热点”。

 

  —— Mike Morton ,马萨诸塞州波士顿

 

  【代码规模守恒定律】当你为了加速,把一页代码变成几条简单的指令时,请不要忘了增加注释,以使源码的行数保持为一个常量。

 

  —— Mike Morton ,马萨诸塞州波士顿

 

  如果程序员自己模拟实现一个构造比编译器本身实现那个构造还要快,那编译器的作者也太失败了。

 

  —— Guy L. Steele, Jr. , Tartan 实验室

 

  要加速一个 I/O 密集型的程序,请首先考虑所有的 I/O 。消除那些不必要的或冗余的 I/O ,并使余下的部分尽可能地快。

 

  —— David Martin ,宾夕法尼亚州诺里斯敦

 

  最快的 I/O 就是不 I/O 。

 

  —— Nils-Peter Nelson ,贝尔实验室

 

  

 

  那些最便宜、最快而且可靠性最高的计算机组件压根儿就不存在。

 

  —— Gordon Bell , Encore 计算机公司

 

  大多数的汇编语言都有循环操作,用一条机器指令进行一次比较并分支;尽管这条指令是为循环设计的,但在做普通的比较时往往也能派上用场,而且很有效。

 

  —— Guy L. Steele, Jr. , Tartan 实验室

 

  【编译器作者箴言——优化步骤】把一个本来就错了的程序变得更糟绝不是你的错。

 

  —— Bill McKeeman ,王安公司

 

  电每纳秒传播一英尺。

 

  —— Grace Murray Hopper ,美国海军准将

 

   Lisp 程序员知道所有东西的值,却不知道那些东西的计算成本。

 

  —— Alan Perlis ,耶鲁大学

 

   6.5  文档

 

  【否定测试】如果一句话反过来就必然不成立,那就根本没必要把这句话放进文档。

 

  —— Bob Martin , AT&T 公司

 

  当你试图解释一条命令、一个语言特性或是一种硬件的时候,请首先说明它要解决什么问题。

 

  —— David Martin ,宾夕法尼亚州诺里斯敦

 

  【一页原则】一个 { 规格说明、设计、过程、测试计划 } 如果不能在一页 8.5 英寸× 11 英寸的纸 上写明白,那么这个东西别人就没办法理解。

 

  —— Mark Ardis ,王安公司

 

  纸上的工作没结束,整个工作也就还没结束。

 

  ——佚名

 

   6.6  软件管理

 

  系统的结构反映出构建该系统的组织的结构。

 

  —— Richard E. Fairley ,王安公司

 

  别坚持做那些没用的事。

 

  ——佚名

 

  【 90 — 90 法则】前 90% 的代码占用了 90% 的预定开发时间,余下的 10% 代码又花费了 90% 的预定开发时间 。

 

  —— Tom Cargill ,贝尔实验室

 

  只有不到 10% 的代码用于完成这个程序表面上的目的,余下的都在处理输入输出、数据验证、数据结构维护等家务活。

 

  —— Mary Shaw ,卡内基—梅隆大学

 

  正确的判断来源于经验,然而经验来源于错误的判断。

 

  —— Fred Brooks ,北卡罗来纳大学

 

  如果有人基本上做出了你想要做的东西,你就没必要自己写一个新程序。就算你非写不可,也请尽可能多地利用现有的代码。

 

  —— Richard Hill ,惠普公司(瑞士日内瓦)

 

  代码能借用就借用。

 

  —— Tom Duff ,贝尔实验室

 

  与客户保持良好的关系可以使生产率加倍。

 

  —— Larry Bernstein ,贝尔通信研究院

 

  把一个现有成熟程序转移到一种新语言或者新平台,只需要原来开发的十分之一的时间、人力、成本。

 

  —— Douglas W. Jones ,艾奥瓦大学

 

  那些用手做就已经很快了的事情,就不要用计算机去做了。

 

  —— Richard Hill ,惠普公司(瑞士日内瓦)

 

  那些能用计算机迅速解决的问题,就别用手做了。

 

  —— Tom Duff ,贝尔实验室

 

  我想写的程序不只是程序,而且是会写程序的程序。

 

  —— Dick Sites , DEC 公司

 

  【 Brooks 原型定律】计划好抛弃一个原型,这是迟早的事。

 

  —— Fred Brooks ,北卡罗来纳大学

 

  如果开始就打算抛弃一个原型,那恐怕你得抛弃两个。

 

  —— Craig Zerouni , Computer FX 公司(英国伦敦)

 

  原型方法可以将系统开发的工作量减少 40% 。

 

  —— Larry Bernstein ,贝尔通信研究院

 

  【 Thompson 望远镜学徒定律】先做一个 4 英尺镜片的(望远镜),再做一个 6 英尺镜片的,这比直接做 6 英尺镜片的更省时间。

 

  —— Bill McKeeman ,王安公司

 

  拼命干活无法取代理解。

 

  —— H. H. Williams ,加州奥克兰

 

  做事应该先做最难的部分。如果最难的部分无法做到,那还在简单的部分上浪费时间干嘛?一旦困难的地方搞定了,那你就胜利在望了。

 

  做事应该先做最简单的部分。你开始所预想的简单部分,做起来可能是很有难度的。一旦你把简单的部分都做好了,你就可以全力攻克最难的部分了。

 

  —— Al Schapira ,贝尔实验室

 

   6.7  其他

 

  【 Sturgeon 定律——在科幻小说和计算机科学中同等适用】毫无疑问, 90% 的软件都没什么用。这是因为对任何东西而言,其中的 90% 都是没什么用的。

 

  —— Mary Shaw ,卡内斯—梅隆大学

 

  对计算机撒谎是要受到惩罚的。

 

  —— Perry Farrar ,马里兰州

 

  如果不要求系统可靠,它可能做任何事情。

 

  —— H. H. Williams ,加州奥克兰

 

  一个人的常量是另一个人的变量。

 

  —— Susan Gerhart , Microelectronics and Computer Technology 公司

 

  一个人的数据就是另一个人的程序。

 

  —— Guy L. Steele, Jr. , Tartan 实验室

 

  【 KISS 法则】用最简单、最笨的方法做事 。

 

  ——佚名

 

   6.8  原理

 

  看到这里,你一定会接受下面这条不错的箴言:

 

  别轻信那些看似聪明的法则。

 

  —— Joe Condon ,贝尔实验室

 

   6.9  习题

 

  尽管本章中对每一条箴言只用了寥寥数语,但其中大多数是可以很大程度上进行扩写的(比如说,可以扩写为一篇本科生的论文,或是一场酒酣之余的高谈阔论)。下面的问题告诉我们应该如何扩展这些箴言。

 

  先让程序跑起来,再考虑怎么让程序跑得快。

 

  —— Bruce Whiteside ,伊利诺伊州伍德里奇

 

  

 

  你们的“作业”就是用类似的方式扩写其他箴言。

 

   1. 用更精确的语言重新表述箴言。上面的实例可以扩写为:

 

  在确定程序的正确性之前,请忽略程序的效率。

 

  或是

 

  如果程序不能工作,那运行再快也没用;毕竟,一个总是给出错误结论的空程序是根本不花时间的。

 

   2.      举一个小而具体的实例支持你的表述。 Kernighan 和 Plauger 在 Elements of Programming Style 第 7 章中列出了从一个程序源码中截出的 10 行纠结而难以理解的代码。这段绕人的代码节省了一次比较,却引入了一个小错误。通过“浪费”时间进行一次本可以节省的比较,他们把十行晦涩的代码变成了两行一目了然的代码。从这一现实教训中,他们总结出下面这个道理:

 

  欲求快,先求对。

 

   3.      寻找这些箴言用于大型程序设计的“实战故事”。

 

   a.      我很高兴这个箴言对于项目实践有所帮助。比如, 1.2 节描述的几个例子,对系统所进行的性能监视指向了程序执行的关键点,然后我们可以通过简单调整这些关键点来提高系统性能。

 

   b.      忽略这些箴言,可能导致灾难性的结果。 20 世纪 60 年代初, Vic Vyssotsky 修改一个 Fortran 编译器源码,想让一个原本正确的程序更加快速,却因而引入了一个程序错误。两年过去了,这个程序错误一直没有被发现,因为在 100 000 次编译中连一次也没有调用这个程序。 Vyssotsky 花在这次不成熟的优化上面的时间比仅仅浪费时间更加糟糕,因为他使一个原本好好的程序出错了。(不过,这个故事教育了 Vyssotsky 和贝尔实验室一代又一代程序员。)

 

   4.      请评价这些箴言。哪些是“不变的真理”,哪些在某些情况下会产生误导?有一次我曾经对 Tartan 实验室的 Bill Wulf 说起“如果程序不能工作,运行得再快也没有用”这句话,我觉得这个是无可争议的事实。这时他举了一个我们都在用的文档格式化程序的例子。尽管这个程序比其前一版本明显快很多,但在有些时候会慢得难以忍受,比如编译一本书要花上好几个小时。 Wulf 用下面这个论据赢得了这场论战:“正如其他所有大型系统一样,这一程序有 10 个记录在案的轻微程序错误,而下个月它又将会有 10 个新的小错误被我们发现。如果给你机会进行选择,你是要解决现在已知的 10 个小错误,还是让程序快 10 倍呢?”

 

   6.10  深入阅读


  如果你想要更多这样言简意赅的箴言,请看看 Tom Parker 的 Rules of Thumb ( Houghton Mifflin , 1983 )。该书的封面上有如下法则:

 

   798. 一个鸵鸟蛋足够作为 24 人的早餐加午餐。

 

   886. 潜水艇能以最高效率在水下行动的条件是:它的长宽比在 10 到 13 之间。

 

  这本书上一共有 896 条类似的法则。

 

   Butler Lampson 发表在 IEEE Software 1 ( 1984 年 1 月)上的“ Hints for Computer System Design ”中有不少非常有用的“经验法则”:

 

  把通常情况和最坏情况分开处理。

 

  在分配资源的时候,请努力避免引起灾难,而不是妄图获得最优。

 

   Lampson 构建了数十套最先进的软硬件系统,他的这些提示总结了自己的经验。