您是否曾经……
浪费了无数时间撰写算法,到头来才发现是错的?
使用了某种数据结构,其实是过分复杂了?
测试了某段程序,却未能发现某个明显不过的问题?
花了一整天定位某个缺陷,后来发现本来应该只用五分钟就找到?
被逼着把程序提速三倍,还规定只能用更少的内存?
为把程序从工作站和PC之间来回移植而斗争?
试图对别人编写的程序做相当程度的改动?
由于无法理解某个程序,只能重写一遍?
这些事情有意思吗?
这些都是程序员每时每刻都要面对的工作。但真动手做这些事情时,却往往发现它们不像预料中的那般容易,因为像测试、调试、可移植性、性能、设计取舍,以及格调——统称程序设计实践的这些话题,在计算机科学或是程序设计课程中通常并不受关注。大多数程序员通过不断地踩坑跌跤学到了一些只鳞片甲,还有一些人则始终对此一无所知。
在这个接口多如牛毛、工具、语言和系统瞬息万变、所有事物的增长压力都丝毫无法妥协的世界,人们会迷失方向,不再坚持一些基本原则——简单性、清晰性和通用性——而这些才是构建起优质软件的基石。人们也常常忽视了工具和记法的价值,而这些可以使得某些软件构造过程自动化,驱使计算机自行完成程序设计。
本书中,我们的论述正是基于这些根本的、互相联系的原则,它们适用于计算的所有层次。这些原则包括简单性——保持程序短小精悍、方便管理;清晰性,保证程序无论是对人还是对机器都易于理解;通用性,意思是让程序在众多场景下都能工作,并在新的场景出现时有着良好的可适配性;还有自动化,让机器完成工作,把我们从琐碎的工作中解放出来。在审视了计算机程序设计中的设计、调试、测试和性能调优所涉及的大量的语言、算法和数据结构后,我们终于能够勾勒出一些放之四海而皆准的工程概念,而并不依赖于具体的语言、操作系统或程序设计范式。
本书来源于多年经验,包括编写和维护大量软件、教授程序设计课程以及与各种不同的程序员一道工作。我们想要分享实践中遭遇的教训、传承实践经验带来的洞见,以及向所有层次的程序员提出一些建议,使他们变得更加高效和高产。
本书的目标读者有好几类。如果你是一个在校学生,上过一两门程序设计课程,并意欲成为一名更好的程序员,本书将拓展一些在学校中来不及深入讨论的课程。如果你的工作中会涉及一些程序设计,但目的是为了支持其他内容而非程序设计自身,本书能提供如何更加高效程序设计的信息。如果你是一名专业程序员,但在学校里没有充分接触过相关内容或是想温故而知新,又抑或你是一名软件经理,想要带领团队走上正确道路,那么本书的材料将会对你很有价值。
我们希望本书中的建议能够帮助你写出更好的程序。唯一的前提要求是你之前写过程序,最好使用过C、C++或Java。当然,你的经验越多,就越容易理解。没有任何东西能让你只用21天就从小白变身专家。UNIX和Linux程序员会发现有些例程比起只用过Windows和Macintosh系统的程序员来更熟悉些,但任何程序设计环境的程序员都会发现一些让自己的日子更好过的内容。
全书内容组织在九章内,每章聚焦在程序设计的一个重要方面。
第一章讨论的是编程格调。好的格调对于好的程序设计来说至关重要,所以我们决定一开始的讨论就涵盖这个问题。写得好的程序比写得差的程序具有更好的品质——它们包含的错误更少,调试和修改都更容易,所以一上手就考虑格调问题才如此重要。本章还介绍了与好的程序设计相关的一个重要主题,即注意运用所采用语言中适当的习惯用法(idiom)。
算法和数据结构,这是第二章的话题,也是计算机科学核心科目以及程序设计课程中的主要部分。由于大部分读者对这些材料应该都有所了解,我们的讨论意在对于几乎在每个程序中都会出现的若干算法和数据结构作一个简明扼要的回顾。更复杂的算法和数据结构通常都是从这些构件之上演化出来的,所以掌握这些基本内容尤其必要。
第三章描述了一个小程序的设计和实现,这个过程揭示了实际设定中遇到的算法和数据结构问题。这个程序使用了五种语言来反复实现,而通过这些不同版本的对比,说明了在每一种语言中是如何来把握同一种数据结构的,同时也讲述了它们在一系列语言中所耗用资源、所表现性能的异同。 用户、程序和程序片断之间的接口,在程序设计中位于基础性地位,而衡量软件成功程度的主要指标就是看接口设计和实现得如何。第四章展示了一个小型库的演化,该库用以解析某种被广泛使用的数据格式。虽然这个例子规模不大,它却揭示了接口设计中的很多令人关切的问题:抽象、信息隐藏、资源管理以及错误处理等。 无论我们多么努力地尝试首次就把程序写对,缺陷,以及随之而来的调试,都是不可避免的。第五章为实现系统高效的调试,给出了对应的战略和战术。讨论的话题包括常见缺隐的特征,以及“数字命理学”的重要性,亦即调试输出的模式常常指示着问题的藏身之所。
测试的实质是意欲建立一种经得起推敲的机制,来保证程序以正确方式工作,并且在程序的变迁过程中始终保持其正确性。第六章强调了手动和自动地进行系统化测试的课题。边界条件测试探测的是程序的潜在弱点。机械化设施和测试脚手架使得只须投入适量的精力就可以完成大量的测试工作。压力测试实现的是典型用户不会执行的测试类型,并且可以发掘出一类完全不同的缺陷。
由于计算机的运行速度是那样地快,编译器又是那样地强大,很多程序一写好就运行得足够快了。然而还是有一些程序运行得过慢,或是耗用了过多的内存,或是这两种不好的症状同时出现。第七章给出一种井然有序的途径,来完成让程序高效利用资源的任务,而且在程序变得高效的同时,还能保持其正确性和稳定性。
第八章讨论了可移植性的方方面面。成功的程序的寿命很长,长到它们的运行环境会发生改变,或是被迁移到新的系统、新的硬件和新的国家。可移植性的目标在于减少程序的维护量,手段是尽可能减少适配新环境时的必要改动量。
计算的语言是丰富的,不仅有我们在编写大批程序时所采用的通用语言,还有很多在较窄领域所采用的专用语言。第九章给出了若干例子来说明计算中涉及的记法的重要性,还演示了如何运用这些记法来简化程序、指导实现,甚至帮助我们写出能够写程序的程序。
为了谈论程序设计,我们必须展示大量代码。大部分例子都是为本书内容而专门写就,但也有一些小例子摘自其他来源。我们尽力把书中这些自用的代码打磨成较高的品质,并把这些程序采用机器可读的文本形式直接在半打系统上进行了测试。更多信息请参见《程序设计实践》官网:
http://tpop.awl.com
大多数书中的程序是用C语言写的,也有一些例子是用C++、Java等语言写的,甚至还有一些涉及到了脚本语言。在最底层,C和C++基本上一模一样,而我们写的所有C程序也同时是合法的C++程序。C++和Java都是C语言的直系衍生语言,继承了后者的一点点语法,以及其大部分的效率和表达力,同时加入了更丰富的类型系统和库。
在我们自己的工作中,我们常规地使用所有这三种语言,还会用到很多其他的语言。语言的选择依赖于要解决的问题:操作系统最好采用高效的、无限制的语言,如C和C++;快速原型则用像Awk和Perl这样的命令行解释器或脚本语言来写最容易;要编写用户界面的话,则Visual Basic和Tcl/Tk会是有力的竞争者,在这方面Java也可以算一个选择。
在为我们的例子选择语言时,存在一个重要的教学上的困难。正如没有语言能够同样好地解决一切问题,也没有任何一种语言能够在说明所有主题时都能做到最好。高级语言会事先设定一些设计取舍。而如果我们采用了较低级的语言时,就需要考虑一些问题可能会有的不同答案。而只有把这些细节暴露出来,我们才能够更好地谈论这些答案。经验告诉我们,即使我们使用了高级语言提供的基础设施,了解与之相关的低级问题也是无价的。如果没有那样的洞见,很容易就会遭遇性能问题或是莫名其妙和程序行为。这就是为什么我们通常使用C语言来编写我们的例子,尽管在实践中我们可能选用其他的语言。
不过,对于本书的大部分章节来说,取得的经验教训并不和任何特定的程序设计语言相关。数据结构的选择深受所采用的语言影响——有些语言中根本没有提供该数据结构,而在另一些语言中则可能对该数据结构了提供好几种支持。但是做取舍的途径,却是相同的。测试和调试的细节在不同的语言中各各不同,但是战略和战术却是大体相似的。使得程序变得高效的技术,适用于任何一种语言。
不管你采用何种语言来进行程序设计,作为程序员,你的任务就是利用现成的工具做到最好。一个好的程序员能够克服差劲的语言和臃肿的操作系统带来的困难,而即使是一个优秀的程序设计环境也无法拯救一个差劲的程序员。我们希望,无论你现在的经验和技能如何,本书都可以帮助你改善程序设计,并更好地享受程序设计之乐。
我们向阅读了书稿并给予我们诸多有用回馈的朋友和同事深深致谢。Jon Bentley、Russ Cox、John Lakos、John Linderman,、Peter Memishian、Ian Lance Taylor、Howard Trickey和Chris Van Wyk都非常用心地通读了书稿,有人还不止一遍。我们亦极大受惠于Tom Cargill、Chris Cleeland、Steve Dewhurst、Eric Grosse、Andrew Herron、Gerard Holzmann、Doug McIlroy、Paul McNamee、Peter Nelson、Dennis Ritchie、Rich Stevens、Tom Szymanski、Kentaro Toyama、John Wait、Daniel C. Wang、Peter Weinberger、Margaret Wrigh和Cliff Young,他们的对于草稿中的很多地方提出了无价的建议。同样,我们也感谢以下人士的美好意见和用心建议:Al Aho、Ken Arnold、Chuck Bigelow、Joshua Bloch、Bill Coughran、Bob Flandrena、Renée French、Mark Kernighan、Andy Koenig、Sape Mullender、Evi Nemeth、Marty Rabinowitz、Mark V. Shaney、Bjarne Stroustrup、Ken Thompson和Phil Wadler。感谢你们所有人。 Brian W. Kernighan Rob Pike