你是否曾经……
枉费多时,依照有错的算法编写代码?
使用过于复杂的数据结构?
测试程序,却遗漏明显的问题?
耗费整整一天来定位错误,而它本应在五分钟内就被查到?
需要编写出更快更省内存的程序 ?
殚精竭虑,在工作站和PC之间移植程序?
试图适度修改他人的程序?
仅仅因无法理解某个程序而不得不重写?
你是否曾经……
枉费多时,依照有错的算法编写代码?
使用过于复杂的数据结构?
测试程序,却遗漏明显的问题?
耗费整整一天来定位错误,而它本应在五分钟内就被查到?
需要编写出更快更省内存的程序 ?
殚精竭虑,在工作站和PC之间移植程序?
试图适度修改他人的程序?
仅仅因无法理解某个程序而不得不重写?
好玩吗?
程序员面临这种种困扰,挥之不去。可是这些问题出人意料地棘手,因为诸如测试、除错、移植、性能、设计取舍 和 编码风格 的议题 —— 亦即程序设计实践 —— 通常并非计算机科学 或 程序设计课程的重点。日积月累,大多数程序员零零散散习得了处理方法,少数却始终毫无长进。
现实世界中,程序接口纷繁芜杂,工具、语言和系统不断变化,方方面面都有重重压力,让人一叶障目,忽视了基本原则 —— 简洁、清晰、通用 —— 他们都是优质软件的基石。人们也可能忽略了工具和注记方式的价值,正是这两者让软件创建的部分过程得以自动化,使得计算机能为自身设计程序。
本书中解决问题的手法 以 这些相互交织的原则为基础,他们适用于计算机行业的所有层面。简洁原则,使得程序短小精悍又便于掌控;清晰原则,保证人和机器都能轻松理解程序;通用原则,让程序在广泛场景下妥善运作,而且便于修改以适应新状况;还有,自动化原则,让机器替人工作,让我们从枯燥的工作中脱身。凭借各种语言,从算法和数据结构出发,通过设计、除错、测试 和 性能调优,审视计算机程序设计,本书得以阐明具有普遍意义的工程理念,这些理念并不依赖于特定的语言,操作系统或编程范式。
本书源出多年实践,其中有:编写和维护大量软件、教授程序设计课程 以及 与各式各样程序员共事。我们想分享实际问题的教训,传播从经验总结的见解,并对程序员提出建议,不论其水平如何,都有助于精熟技艺,提升生产力。
本书面向几类读者。你若是学生,研修过一两门程序设计课程,有志成为更优秀的程序员,本书将扩展一些在校内无暇顾及的议题。假使你的部分工作是编写程序,其本身不是目的,而在于支援其他业务,本书的信息将有助你提高编程效率。如果你身为职业程序员,不巧上学时未能充分接触以上议题,或你想温故知新;又或者,你是软件经理,有意往正确方向引导部下,这本材料将有用武之地。
我们希望这些建议将助你写出更优质的程序。唯一的前提是你应当写过程序,最好是C、C++或Java。诚然,你的经验越丰富,读起来就越轻松;没有诀窍能让你只花21天就从新手变成专家。相比之下, Unix和Linux程序员会更熟悉某些用例,而只用过Windows和Macintosh系统的程序员则可能稍嫌生疏,但无论程序员处于何种编程环境,本书将让他们更得心应手。
本书编排成九个章节,每章着眼于程序设计实践的某一重大方面。
第1章讨论编程风格。要写出好程序,良好的风格至关重要,因此我们选定这个议题单刀直入。比起糟糕的程序,编写得法的程序要好得多 —— 错误更少,更易于除错和修改 —— 所以,开宗明义考量编程风格是重中之重。这章还介绍了编写优质程序的一个重要主题,即适合所用语言的一些常用技法。
第2章的议题是算法和数据结构,同样也是 计算机科学的核心科目 和 程序设计课程 的重要部分。由于读者其实大都熟知这些材料,我们只是简要回顾了几种最常用算法和数据结构,他们在程序设计中几乎无处不在,更复杂的算法和数据结构通常都从这些构件演化而成,所以这些基础理应熟练掌握。
第3章详述了一个小程序的设计和实现,用以讲解现实场景下的算法和数据结构。这个程序用五种语言分别重复实现,籍由对比不同版本来说明,相同的数据结构在各种语言中应如何处理,以及因语言不同而导致的表达能力和性能的差异。
用户、程序和程序组件三者之间的接口是程序设计的基础。接口的设计与实现是否得当,很大程度上决定了软件的成败。第4章展现了一个小程序库的演化,他被用作解析一种广泛使用的数据格式。例子虽小,却阐明了接口设计的不少要点:抽象、信息隐藏、资源管理 和 错误处理。
虽然我们大都想一次过写出正确的程序,无奈错误在所难免,于是除错必不可少。第5章给出了系统化的高效除错的策略和技法。这些议题包括了 常见错误的特征 和“数理推断”的重要意义,后者是指,通常凭借分析调试的输出模式以指明问题所在。
测试的目的在于合理有效地保证程序正确运行,即便程序演化,其依然保持正确。第6章的重点是手工和自动两种系统化测试。边界条件测试用于探查潜藏的薄弱环节。自动化和测试框架使得全方位测试简单易行而无需大费周章。压力测试提供的方法有别于典型的用户行为,能排查一类不同寻常的错误。
计算机迅驰如飞,编译器强劲卓绝,得益于此,许多程序从编写完成的一刻起就高速运行。不过也有程序性能低下,或内存耗费过多,甚至二者兼有。第7章展示了一种循序渐进的方法,让程序有效利用计算资源,既提升效率,又正确运行如常。
第8章涵盖了可移植性。成功的程序寿命长久,无论运行环境变迁,还是被移植以适应新系统、新硬件或新国度,其依旧照常运行。可移植性旨在降低维护程序的成本,方法是尽量缩减将其适应新环境的必要更改。
计算机程序设计语言种类丰富,不仅存在广泛用于常规编程的通用语言,还有很多着眼于狭小领域的专用语言。第9章通过几个范例呈现了注记方式于程序设计的重要意义,并且说明了我们能如何运用,来简化程序,指引实现,甚至帮助编写自动生成代码的程序。
为了讨论程序设计,我们要展示大量代码。本书大多数例子都为此专门编写,而一些小例子则改编自其他资料。我们尽力写出优良的代码,这些源码文本能被机器直接读入并编译,并在数种系统上测试通过。请从本书网站“the practice of programming”获取更多资讯:http://tpop.awl.com
本书的程序主要以C编写,兼用了C++和Java,还有些小旁例使用了脚本语言。C和C++在最底层几乎一致,书中的C程序同时也是有效的C++程序。C++和Java都是C的直系后裔,不少语法相同,同时具备C的高效和表达能力,还增加了更丰富的类型系统和程序库。我们(原作者)在自己的日常工作中例行使用这三种语言,也用到很多其他语言。语言的选择视具体问题而定:高性能、无限制的语言,如C或C++,最适合编写操作系统;开发快速原型的最简单易行的工具通常是命令解释器或脚本语言,如AWK或Perl;Visual Basic和Tcl/Tk是构建用户界面的强力竞争者,Java亦然。
我们选择语言编写用例的时候,有个出于教学方法的重要考虑。不存在所向无敌的语言,能很好地解决全部问题;也没有唯一一种语言面面俱圆,最适合解说所有议题。高级语言会预设某些设计取舍。假若采用较为低级的语言,我们就要考虑解决问题的其他可行做法;展现的细节越充分,讨论就越能深入。根据经验,即使我们用到了高级语言的特性,仍然非常值得了解他们和底层实现如何关联。若未能洞悉,程序就容易发生性能问题和异常行为。故此我们的例子频频使用C,尽管现实中我们也许会作出其他选择。
然而本书内容大部分都不依赖于特定语言。数据结构的选择取决于采用的语言;某些语言中可用的数据结构非常有限,而其他语言却提供多种支持。不过选择的方法完全一样。测试和除错的细节因语言而异,但总体而言,策略与技法大同小异。大部分程序性能调优的技术适用于任何语言。
身为程序员,不管使用什么语言,你的任务就是充分利用手头的工具,全力以赴。优秀的程序员能克服差劲的语言或拙劣的操作系统,反之则不然,再完善的编程环境对蹩脚程序员也于事无补。我们希望,不论你积累了多少经验技巧,本书都将助你提升编程技能,而且乐在其中。
我们的朋友和同事审读了手稿,给出了许多有用的建议,我们深表谢意:Jon Bentley、Russ Cox、John Lakos、John Linderman、Peter Memishian、lan 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 Wright和Cliff Young。
我们同样感激下列同仁,感谢他们的良言忠告和用心建议:Al Aho、Ken Arnold、Chuck Bigelow、Joshua Bloch、Bill Coughran、Bob Flandrena、Renee French、Mark Kernighan、Andy Koenig、Sape Mullender、Evi Nemeth、Many Rabinowitz、Mark V. Shaney、Bjarne Stroustrup、Ken Thompson和Phil Wadler。
谢谢你们
Brian W. Kernighan
Rob Pike