去熟悉shell,你会发现自己的生产率迅速提高。需要创建你的Java代码显式导入的全部软件包的列表(重复的只列出一次)?下面的命令将其存储在叫做“list”的文件中:grep '^import ' *.java |sed -e's/.*import *//' -e's/;.*$//' |sort -u >list 如果你没有花大量时间研究过你所用系统上的命令shell的各种能力,这样的命令会显得很吓人。但是,投入一些精力去熟悉你的shell,事情很快就会变得清楚起来。多使用你的命令shell,你会惊讶它能使你的生产率得到怎样的提高。shell实用程序与Windows系统 尽管随Windows系统提供的命令shell在逐步改进,Windows命令行实用程序仍然不如对应的Unix实用程序。但是,并非一切都已无可挽回。 Cygnus Solutions公司有一个叫做Cygwin[URL 31]的软件包。除了为Windows提供Unix兼容层以外,Cygwin还带有120多个Unix实用程序,包括像ls、grep和find这样的很受欢迎的程序。你可以自由下载并使用这些实用程序和库,但一定要阅读它们的许可。随同Cygwin发布的还有Bash shell。在Windows下使用Unix工具 在Windows下有高质量的Unix工具可用,这让我们很高兴;我们每天都使用它们。但是,要注意存在一些集成问题。与对应的MS-DOS工具不同,这些实用程序对文件名的大小写敏感,所以ls a*.bat不会找到AUTOEXEC.BAT。你还可能遇到含有空格的文件名、或是路径分隔符不同所带来的问题。最后,在Unix shell下运行需要MS-DOS风格的参数的MS-DOS程序时,会发生一些有趣的问题。例如,在Unix下,来自JavaSoft的Java实用程序使用冒号作为CLASSPATH分隔符,而在MS-DOS下使用的却是分号。结果,运行在Unix机器上的Bash或ksh脚本在Windows下也同样能运行,但它传给Java的命令行却会被错误地解释。 另外,David Korn(因Korn shell而闻名)制作了一个叫做UWIN的软件包。其目标与Cygwin相同——它是Windows下的Unix开发环境。UWIN带有Korn shell的一个版本。也可从Global Technologies, Ltd.[URL 30]获取商业版本。此外,AT&T提供了该软件包的自由下载版本,用于评估和学术研究。再次说明,在使用之前要先阅读它们的许可。 最后,Tom Christiansen(在本书撰写的同时)正在制作Perl Power Tools,尝试用Perl可移植地实现所有常见的Unix实用程序[URL 32]。相关内容:l 无处不在的自动化,230页挑战l 你目前是否在GUI中用手工做一些事情?你是否曾将一些说明发给同事,其中涉及许多“点这个按钮”、“选哪一项”之类的步骤?它们能自动化吗?l 每当你迁往新环境时,要找出可以使用的shell。看是否能把现在使用的shell带过去。l 调查各种可用于替换你现在的shell的选择。如果你遇到你的shell无法处理的问题,看其他shell是否能更好地应对。16 强力编辑 先前我们说过,工具是手的延伸。噢,与任何其他软件工具相比,这都更适用于编辑器。你需要能尽可能不费力气地操纵文本,因为文本是编程的基本原材料。让我们来看一些能帮助你最大限度地利用编辑环境的一些常见特性和功能。一种编辑器 我们认为你最好是精通一种编辑器,并将其用于所有编辑任务:代码、文档、备忘录、系统管理,等等。如果不坚持使用一种编辑器,你就可能会面临现代的巴别塔大混乱。你可能必须用每种语言的IDE内建的编辑器进行编码,用“all-in-one”办公软件编辑文档,或是用另一种内建的编辑器发送电子邮件。甚至你用于在shell中编辑命令行的键击都有可能不同。如果你在每种环境中有不同的编辑约定和命令,要精通这些环境中的任何一种都会很困难。 你需要的是精通。只是依次输入、并使用鼠标进行剪贴是不够的。那样,在你的手中有了一个强大的编辑器,你却无法发挥出它的效能。敲击十次<-或BACKSPACE,把光标左移到行首,不会像敲击一次^A、Home或0那样高效。提示22Use a Single Editor Well用好一种编辑器 选一种编辑器,彻底了解它,并将其用于所有的编辑任务。如果你用一种编辑器(或一组键绑定)进行所有的文本编辑活动,你就不必停下来思考怎样完成文本操纵:必需的键击将成为本能反应。编辑器将成为你双手的延伸;键会在滑过文本和思想时歌唱起来。这就是我们的目标。 确保你选择的编辑器能在你使用的所有平台上使用。Emacs、vi、CRiSP、Brief及其他一些编辑器可在多种平台上使用,并且常常既有GUI版本,也有非GUI(文本屏幕)版本。编辑器特性 除了你认为特别有用、使用时特别舒适的特性之外,还有一些基本能力,我们认为每个像样的编辑器都应该具备。如果你的编辑器缺少其中的任何能力,那么你或许就应该考虑换一种更高级的编辑器了。l 可配置。编辑器的所有方面都应该能按你的偏好(preference)配置,包括字体、颜色、窗口尺寸以及键击绑定(什么键执行什么命令)。对于常见的编辑操作,与鼠标或菜单驱动的命令相比,只使用键击效率更高,因为你的手无须离开键盘。l 可扩展。编辑器不应该只因为出现了新的编程语言就变得过时。它应该能集成你在使用的任何编译器环境。你应该能把任何新语言或文本格式(XML、HTML第9版,等等)的各种细微差别“教”给它。l 可编程。你应该能对编辑器编程,让它执行复杂的、多步骤的任务。可以通过宏或内建的脚本编程语言(例如,Emacs使用了Lisp的一个变种)进行这样的编程。此外,许多编辑器支持针对特定编程语言的特性,比如:l 语法突显l 自动完成l 自动缩进l 初始代码或文档样板l 与帮助系统挂接l 类IDE特性(编译、调试,等等) 像语法突显这样的特性听起来也许像是无关紧要的附加物,但实际上却可能非常有用,而且还能提高你的生产率。一旦你习惯了看到关键字以不同的颜色或字体出现,远在你启动编译器之前,没有以那样的方式出现的、敲错的关键字就会在你面前跳出来。 对于大型项目,能够在编辑器环境中进行编译、并直接转到出错处非常方便。Emacs特别擅长进行这种方式的交互。生产率 我们遇到的用Windows notepad编辑源码的人数量惊人。这就像是把茶匙当做铁锹——只是敲键和使用基本的基于鼠标的剪贴是不够的。 有什么样的事情需要你做,你却无法以这样的方式做到呢? 嗯,让我们以光标移动的例子作为开始。与重复击键、一个字符一个字符或一行一行移动相比,按一次键、就以词、行、块或函数为单位移动光标,效率要高得多。 再假设你在编写Java代码。你想要按字母顺序排列import语句,而另外有人签入(check in)了一些文件,没有遵守这一标准(这听起来也许很极端,但在大型项目中,这可以让你节省大量时间,不用逐行检查一大堆import语句)。你想要快速地从头到尾检查一些文件,并对它们的一小部分区域进行排序。在像vi和Emacs这样的编辑器中,你可以很容易完成这样的任务(参见图3.1)。用notepad试试看!图3.1 在编辑器中对文本行进行排序 有些编辑器能帮助你使常用操作流水线化。例如,当你创建特定语言的新文件时,编辑器可以为你提供模板。其中也许包括:l 填好的类名或模块名(根据文件名派生)l 你的姓名和/或版权声明l 该语言中的各种构造体(construct)的骨架(例如,构造器与析构器声明) 自动缩进是另一种有用的特性。你不必(使用空格或tab)进行手工缩进,编辑器会自动在适当的时候(例如,在敲入左花括号时)为你进行缩进。这一特性让人愉快的地方是,你可以用编辑器为你的项目提供一致的缩进风格[20]。然后做什么 这种建议特别难写,因为实际上每个读者对他们所用编辑器的熟悉程度和相关经验都有所不同。那么,作为总结,并为下一步该做什么提出一些指导方针,在下面的左边一栏中找到与你的情况相符的情况,然后看右边一栏,看你应该做什么。如果这听起来像你……那么考虑……我使用许多不同的编辑器,但只使用其基本特性。选一种强大的编辑器,好好学习它。我有最喜欢的编辑器,但不使用其全部特性。学习它们。减少你需要敲击的键数。我有最喜欢的编辑器,只要可能就使用它。设法扩展它,并将其用于比现在更多的任务。我认为你们在胡说。notepad就是有史以来最好的编辑器。只要你愿意,并且生产率很高,那就这样吧!但如果你发现自己在“羡慕”别人的编辑器,你可能就需要重新评估自己的位置了。有哪些编辑器可用 此前我们建议你掌握一种像样的编辑器,那么我们推荐哪种编辑器呢?嗯,我们要回避这个问题;你对编辑器的选择是一个个人问题(有人甚至会说这是个“信仰问题”!)。但是,在附录A(266页)中,我们列出了许多流行的编辑器和获取它们的途径。挑战l 有些编辑器使用完备的语言进行定制和脚本编写。例如,Emacs采用了Lisp。作为本年度你将学习的新语言之一,学习你的编辑器使用的语言。如果你发现自己在重复做任何事情,开发一套宏(或等价的东西)加以处理。l 你是否知道你的编辑器所能做的每一件事情?设法难倒使用同样的编辑器的同事。设法通过尽可能少的键击完成任何给定的编辑任务。17 源码控制进步远非由变化组成,而是取决于好记性。不能记住过去的人,被判重复过去。 ——George Santayana, Life of Reason 我们在用户界面中找寻的一个重要的东西是UNDO键——一个能原谅我们的错误的按钮。如果环境支持多级撤消(undo)与重做(redo),那就更好了,这样你就可以回去,撤消几分钟前发生的事情。但如果错误发生在上周,而你那以后已经把计算机打开关闭了十次呢?噢,这是使用源码控制系统的诸多好处之一:它是一个巨大的UNDO键——一个项目级的时间机器,能够让你返回上周的那些太平日子,那时的代码还能够编译并运行。 源码控制系统(或范围更宽泛的配置管理系统)追踪你在源码和文档中做出的每一项变动。更好的系统还能追踪编译器及OS版本。有了适当配置的源码控制系统,你就总能够返回你的软件的前一版本。 但源码控制系统(SCCS)能做的远比撤消错误要多。好的SCCS让你追踪变动,回答这样的问题:谁改动了这一行代码?在当前版本与上周的版本之间有什么区别?在这次发布的版本中我们改动了多少行代码?哪个文件改动最频繁?对于bug追踪、审计、性能及质量等目的,这种信息非常宝贵。 SCCS还能让你标识你的软件的各次发布。一经标识,你将总是能够返回并重新生成该版本,并且不受在其后发生的变动的影响。 我们常常使用SCCS管理开发树中的分支。例如,一旦你发布了某个软件,你通常会想为下一次发布继续开发。与此同时,你也需要处理当前发布的版本中的bug,把修正后的版本发送给客户。(如果合适)你想要让这些bug修正合并进下一次发布中,但你不想把正在开发的代码发送给客户。通过SCCS,在每次生成一个发布版本时,你可以在开发树中生成分支。你把bug修正加到分支中的代码上,并在主干上继续开发。因为bug修正也可能与主干有关,有些系统允许你把选定的来自分支的变动自动合并回主干中。 源码控制系统可能会把它们维护的文件保存在某个中央仓库(repository)中——这是进行存档的好候选地。 最后,有些产品可能允许两个或更多用户同时在相同的文件集上工作,甚至在同一文件中同时做出改动。系统随后在文件被送回仓库时对这些改动进行合并。尽管看起来有风险,在实践中这样的系统在所有规模的项目上都工作良好。提示23Always Use Source Code Control总是使用源码控制 总是。即使你的团队只有你一个人,你的项目只需一周时间;即使那是“用过就扔”的原型;即使你的工作对象并非源码;确保每样东西都处在源码控制之下——文档、电话号码表、给供应商的备忘录、makefile、构建与发布流程、烧制CD母盘的shell小脚本——每样东西。我们例行公事地对我们敲入的每一样东西进行源码控制(包括本书的文本)。即使我们不是在开发项目,我们的日常工作也被安全地保存在仓库中。源码控制与构建 把整个项目置于源码控制系统的保护之下具有一项很大的、隐蔽的好处:你可以进行自动的和可重复的产品构建。 项目构建机制可以自动从仓库中取出最近的源码。它可以在午夜运行,在每个人都(很可能)回家之后。你可以运行自动的回归测试,确保当日的编码没有造成任何破坏。构建的自动化保证了一致性——没有手工过程,而你也不需要开发者记住把代码拷贝进特殊的构建区域。 构建是可重复的,因为你总是可以按照源码将给定日期的内容重新进行构建。但我们团队没有使用源码控制 他们应该感到羞耻!听起来这是个“布道”的机会!但是,在等待他们看到光明的同时,也许你应该实施自己私人的源码控制。使用我们在附录A中列出的可自由获取的工具,并确保把你个人的工作安全地保存进仓库中(并且完成你的项目所要求的无论什么事情)。尽管这看起来像是重复劳动,我们几乎可以向你担保,在你须要回答像“你对xyz模块做了什么?”和“是什么破坏了构建?”这样的问题时,它将使你免受困扰(并为你的项目节省金钱)。这一方法也许还能有助于使你们的管理部门确信,源码控制确实行之有效。 不要忘了,SCCS也同样适用于你在工作之外所做的事情。源码控制产品 附录A(271页)给出了一些有代表性的源码控制系统的URL,有些是商业产品,有些可自由获取。还有许多其他的产品可用——你可以在配置管理FAQ中寻求建议。相关内容:l 正交性,34页l 纯文本的力量,73页l 全都是写,248页挑战l 即使你无法在工作中使用SCCS,也要在个人的系统上安装RCS或CVS。用它管理你的“宠物项目”、你撰写的文档、以及(可能的)应用于计算机系统自身的配置变动。l 在Web上有些开放源码项目的存档对外公开(比如Mozilla[URL51]、KDE[URL54]、以及Gimp[URL55]),看一看这样的项目。你怎样获取源文件的更新?你怎样做出改动?——项目是否会对访问进行管制,或是对改动的并入进行裁决?18 调试这是痛苦的事:看着你自己的烦忧,并且知道不是别人、而是你自己一人所致 ——索福克勒斯:《埃阿斯》 自从14世纪以来,bug(虫子、臭虫)一词就一直被用于描述“恐怖的东西”。COBOL的发明者,海军少将Grace Hopper博士据信观察到了第一只计算机bug——真的是一只虫子,一只在早期计算机系统的继电器里抓到的蛾子。在被要求解释机器为何未按期望运转时,有一位技术人员报告说,“有一只虫子在系统里”,并且负责地把它——翅膀及其他所有部分——粘在了日志簿里。 遗憾的是,在我们的系统里仍然有“bug”,虽然不是会飞的那种。但与以前相比,14世纪的含义——可怕的东西——现在也许更为适用。软件缺陷以各种各样的方式表现自己,从被误解的需求到编码错误。糟糕的是,现代计算机系统仍然局限于做你告诉它的事情,而不一定是你想要它做的事情。 没有人能写出完美的软件,所以调试肯定要占用你大量时间。让我们来看一看调试所涉及的一些问题,以及一些用于找出难以捉摸的虫子的一般策略。调试的心理学 对于许多开发者,调试本身是一个敏感、感性的话题。你可能会遇到抵赖、推诿、蹩脚的借口、甚或是无动于衷,而不是把它当做要解决的难题发起进攻。 要接受事实:调试就是解决问题,要据此发起进攻。 发现了他人的bug之后,你可以花费时间和精力去指责让人厌恶的肇事者。在有些工作环境中,这是文化的一部分,并且可能是“疏通剂”。但是,在技术竞技场上,你应该专注于修正问题,而不是发出指责。提示24Fix the Problem, Not the Blame要修正问题,而不是发出指责 bug是你的过错还是别人的过错,并不是真的很有关系。它仍然是你的问题。调试的思维方式最容易欺骗的人是一个人自己。 ——Edward Bulwer-Lytton, The Disowned 在你开始调试之前,选择恰当的思维方式十分重要。你须要关闭每天用于保护自我(ego)的许多防卫措施,忘掉你可能面临的任何项目压力,并让自己放松下来。最重要的是,记住调试的第一准则:提示25Don’t Panic不要恐慌 人很容易恐慌,特别是如果你正面临最后期限的到来、或是正在设法找出bug的原因,有一个神经质的老板或客户在你的脖子后面喘气。但非常重要的事情是,要后退一步,实际思考什么可能造成你认为表征了bug的那些症状。 如果你目睹bug或见到bug报告时的第一反应是“那不可能”,你就完全错了。一个脑细胞都不要浪费在以“但那不可能发生”起头的思路上,因为很明显,那不仅可能,而且已经发生了。 在调试时小心“近视”。要抵制只修正你看到的症状的急迫愿望:更有可能的情况是,实际的故障离你正在观察的地方可能还有几步远,并且可能涉及许多其他的相关事物。要总是设法找出问题的根源,而不只是问题的特定表现。从何处开始 在开始查看bug之前,要确保你是在能够成功编译的代码上工作——没有警告。我们例行公事地把编译器警告级设得尽可能高。把时间浪费在设法找出编译器能够为你找出的问题上没有意义!我们需要专注于手上更困难的问题。 在设法解决任何问题时,你需要搜集所有的相关数据。糟糕的是,bug报告不是精密科学。你很容易被巧合误导,而你不能承受把时间浪费在对巧合进行调试上。你首先需要在观察中做到准确。 bug报告的准确性在经过第三方之手时会进一步降低——实际上你可能需要观察报告bug的用户的操作,以获取足够程度的细节。 Andy曾经参与过一个大型图形应用的开发。快要发布时,测试人员报告说,每次他们用特定的画笔画线,应用都会崩溃。负责该应用的程序员争辩说,这个画笔没有任何问题;他试过用它绘图,它工作得很好。几天里这样的对话来回进行,大家的情绪急速上升。 最后,我们让他们坐到同一个房间里。测试人员选了画笔工具,从右上角到左下角画了一条线。应用程序炸了。“噢”,程序员用很小的声音说。他随后像绵羊一样承认,他在测试时只测试了从左下角画到右上角的情况,没有暴露出这个bug。 这个故事有两个要点:l 你也许需要与报告bug的用户面谈,以搜集比最初给你的数据更多的数据。l 人工合成的测试(比如那个程序员只从下画到上)不能足够地演练(exercise)应用。你必须既强硬地测试边界条件,又测试现实中的最终用户的使用模式。你需要系统地进行这样的测试(参见无情的测试,237页)。测试策略 一旦你认为你知道了在发生什么,就到了找出程序认为在发生什么的时候了。再现bug(reproduction,亦有“繁殖”之意——译注) 不,我们的bug不会真的繁殖(尽管其中有一些可能已经到了合法的生育年龄)。我们谈论的是另一种“再现”。 开始修正bug的最佳途径是让其可再现。毕竟,如果你不能再现它,你又怎么知道它已经被修正了呢? 但我们想要的不是能够通过长长的步骤再现的bug;我们要的是能够通过一条命令再现的bug。如果你必须通过15个步骤才能到达bug显露的地方,修正bug就会困难得多。有时候,强迫你自己隔离显示出bug的环境,你甚至会洞见到它的修正方法。 要了解沿着这些思路延伸的其他想法,参见无处不在的自动化(230页)。使你的数据可视化 常常,要认识程序在做什么——或是要做什么——最容易的途径是好好看一看它操作的数据。最简单的例子是直截了当的“variable name = data value”方法,这可以作为打印文本、也可以作为GUI对话框或列表中的字段实现。 但通过使用允许你“使数据及其所有的相互关系可视化”的调试器,你可以深入得多地获得对你的数据的洞察。有一些调试器能够通过虚拟现实场景把你的数据表示为3D立交图,或是表示为3D波形图,或是就表示为简单的结构图(如下一页的图3.2所示)。在单步跟踪程序的过程中,当你一直在追猎的bug突然跳到你面前时,这样的图远胜于千言万语。 即使你的调试器对可视化数据的支持有限,你仍然自己进行可视化——或是通过手工方式,用纸和笔,或是用外部的绘图程序。 DDD调试器有一些可视化能力,并且可以自由获取(参见[URL 19])。有趣的是,DDD能与多种语言一起工作,包括Ada、C、C++、Fortran、Java、Modula、Pascal、图3.2 一个循环链表的调试器示例图。箭头表示指向节点的指针Perl以及Python(显然是正交的设计)。跟踪 调试器通常会聚焦于程序现在的状态。有时你需要更多的东西——你需要观察程序或数据结构随时间变化的状态。查看栈踪迹(stack trace)只能告诉你,你是怎样直接到达这里的。它无法告诉你,在此调用链之前你在做什么,特别是在基于事件的系统中。 跟踪语句把小诊断消息打印到屏幕上或文件中,说明像“到了这里”和“x的值 = 2”这样的事情。与IDE风格的调试器相比,这是一种原始的技术,但在诊断调试器无法诊断的一些错误种类时却特别有效。在时间本身是一项因素的任何系统中,跟踪都具有难以估量的价值:并发进程、实时系统、还有基于事件的应用。 你可以使用跟踪语句“钻入”代码。也就是,你可以在沿着调用树下降时增加跟踪语句。 跟踪消息应该采用规范、一致的格式:你可能会想自动解析它们。例如,如果你需要跟踪资源泄漏(比如未配平(unbalanced)的open/close),你可以把每一次open和每一次close 记录在日志文件中。通过用Perl处理该日志文件,你可以轻松地确定坏变量?检查它们的邻居 有时你检查一个变量,希望看到一个小整数值,得到的却是像0x6e69614d这样的东西。在你卷起袖子、郑重其事地开始调试之前,先快速地查看一下这个坏变量周围的内存。这常常能带给你线索。在我们的例子中,把周边的内存作为字符进行检查得到的是:20333231 6e69614d 2c745320 746f4e0a1 2 3 M a i n S t , N o t2c6e776f 2058580a 31323433 00000a33o w n , x x 3 4 2 1 3。0。0 看上去像是有人把街道地址“喷”到了我们的计数器上。现在我们知道该去查看什么地方了。有问题的open是在哪里发生的。橡皮鸭 找到问题的原因的一种非常简单、却又特别有用的技术是向别人解释它。他应该越过你的肩膀看着屏幕,不断点头(像澡盆里上下晃动的橡皮鸭)。他们一个字也不需要说;你只是一步步解释代码要做什么,常常就能让问题从屏幕上跳出来,宣布自己的存在。 这听起来很简单,但在向他人解释问题时,你必须明确地陈述那些你在自己检查代码时想当然的事情。因为必须详细描述这些假定中的一部分,你可能会突然获得对问题的新洞见。消除过程 在大多数项目中,你调试的代码可能是你和你们团队的其他成员编写的应用代码、第三方产品(数据库、连接性、图形库、专用通信或算法,等等)、以及平台环境(操作系统、系统库、编译器)的混合物。 bug有可能存在于OS、编译器、或是第三方产品中——但这不应该是你的第一想法。有大得多的可能性的是,bug存在于正在开发的应用代码中。与假定库本身出了问题相比,假定应用代码对库的调用不正确通常更有好处。即使问题确实应归于第三方,在提交bug报告之前,你也必须先消除你的代码中的bug。 我们参加过一个项目的开发,有位高级工程师确信select系统调用在Solaris上有问题。再多的劝说或逻辑也无法改变他的想法(这台机器上的所有其他网络应用都工作良好这一事实也一样无济于事)。他花了数周时间编写绕开这一问题的代码,因为某种奇怪的原因,却好像并没有解决问题。当最后被迫坐下来、阅读关于select的文档时,他在几分钟之内就发现并纠正了问题。现在每当有人开始因为很可能是我们自己的故障而抱怨系统时,我们就会使用“select没有问题”作为温和的提醒。提示26“Select” Isn’t Broken“Select”没有问题 记住,如果你看到马蹄印,要想到马,而不是斑马。OS很可能没有问题。数据库也很可能情况良好。 如果你“只改动了一样东西”,系统就停止了工作,那样东西很可能就需要对此负责——直接地或间接地,不管那看起来有多牵强。有时被改动的东西在你的控制之外:OS的新版本、编译器、数据库或是其他第三方软件都可能会毁坏先前的正确代码。可能会出现新的bug。你先前已绕开的bug得到了修正,却破坏了用于绕开它的代码。API变了,功能变了;简而言之,这是全新的球赛,你必须在这些新的条件下重新测试系统。所以在考虑升级时要紧盯着进度表;你可能会想等到下一次发布之后再升级。 但是,如果没有显而易见的地方让你着手查看,你总是可以依靠好用的老式二分查找。看症状是否出现在代码中的两个远端之一,然后看中间。如果问题出现了,则臭虫位于起点与中点之间;否则,它就在中点与终点之间。以这种方式,你可以让范围越来越小,直到最终确定问题所在。造成惊讶的要素 在发现某个bug让你吃惊时(也许你在用我们听不到的声音咕哝说:“那不可能。”),你必须重新评估你确信不疑的“事实”。在那个链表例程中——你知道它坚固耐用,不可能是这个bug的原因——你是否测试了所有边界条件?另外一段代码你已经用了好几年——它不可能还有bug。可能吗? 当然可能。某样东西出错时,你感到吃惊的程度与你对正在运行的代码的信任及信心成正比。这就是为什么,在面对“让人吃惊”的故障时,你必须意识到你的一个或更多的假设是错的。不要因为你“知道”它能工作而轻易放过与bug有牵连的例程或代码。证明它。用这些数据、这些边界条件、在这个语境中证明它。提示27Don’t Assume it – Prove It不要假定,要证明