代码质量(权威精选植根于开发实践的最佳读物)

 

Jolt大奖精选丛书

代码质量(权威精选植根于开发实践的最佳读物)

(希)斯宾耐立思(Spinellis,D.)著

左飞,吴跃,李洁译

ISBN 978-7-121-17421-6  

20127月出版

定价:89.00

16

512

宣传语:一项技术产品只有在获得了Jolt奖之后才能真正成为行业的主流,一本技术图书只有在获得了Jolt奖之后才能真正奠定经典的地位。

Jolt大奖素有软件业之奥斯卡的美称,本丛书精选自Jolt历届获奖图书,以植根于开发实践中的独到工程思想与杰出方法论为主要甄选方向。Diomidis Spinellis首部著作《代码阅读》(Code Reading)旨在阐明程序员应如何理解与修改代码,与此不同的是,本书重点讨论代码的非功能特性,深入讲述代码如何满足重要的非功能性需求,如可靠性、安全性、可移植性和可维护性,以及时间效率和空间效率。本书从Apache Web应用服务器、BSD UNIX操作系统和HSQLDB Java数据库等开源项目中攫取数百个小例子,并以实例为基准点,辅以理论分析,从实用的角度讲述每个专业软件开发人员能立即运用的概念和技术。

本书荣获2007Jolt大奖,适用于不同知识层次的软件工作、程序开发和研究人员。

出版说明

经久不息的回荡

今时的读书人,不复有无书可读之苦,却时有品种繁多而无从择优之惑,甚而专业度颇高的技术书领域,亦日趋遭逢乱花迷眼的境地。此时,若得觅权威书评,抑或有公信力的排行榜,可按图索骥,大大增加选中好书的命中率。然而,如此良助,不可多得,纵观中外也唯见一枝独秀——素有软件业奥斯卡之美誉的Jolt奖!

震撼世界者为谁

在计算设备已经成为企业生产和日常生活之必备工具的今天,专业和大众用户对于软件的功能、性能和用户体验的要求都在不断提高。在这样的背景下,如何能够发挥出软件开发的最高效率和最大效能,已经是摆在每一个从业者面前的重大课题,而这也正是Jolt大奖横空出世的初衷及坚持数年的宗旨。

Jolt大奖历时20余年,在图书及软件业知名度极高,广受推崇。奖如其名,为引领计算机科学与工程发展主流,Jolt坚持将每年的奖项只颁给那些给整个IT业界带来震撼结果的图书、工具、产品及理念等,因一流的眼光及超高的专业度而得以闻名遐迩,声名远播。

除图书外,Jolt针对软件产品设有诸多奖项分类,如配置管理、协作工具、数据库引擎/数据库工具、设计工具/建模、开发环境、企业工具、库/框架、移动开发工具等。但图书历来是Jolt大奖中最受瞩目且传播最广的一个奖项分支。Jolt曾设有通用类图书、技术类图书等分类,每个分类又设有卓越奖Jolt Award,一般为一个)和生产力奖Productivity Award,一般为23个)。获奖技术图书一经公布,即打上经典烙印,可谓一举震撼全世界(赞助商Jolt可乐的广告词)。

作为计算机技术图书的厚爱者,我们总在追问——是谁在震撼世界,是谁在照亮明天?Jolt大奖恰似摆在眼前的橱窗,让我们可以近距离观看潮流在舞蹈,倾听震撼在轰鸣!

朝花夕拾为哪般

Jolt像是一年一度的承诺,在茫茫书海中为我们淘砺出一批批经得起岁月冲刷的杰作,头顶桂冠的佳作也因而得以一批批引进中国,为国人开阔了眼界,滋补了技术养分。然而,或因技术差距造就的生不逢时、水土不服,或因翻译、制作的不如人意,抑或是疏于宣传等诸多原因,这些经典著作在国内出版后,尽管不乏如获至宝的拥趸,却仍不为诸多人所知,从而与大量本应从中获益的读者擦肩而过。既然这生生错失的遗憾本不该发生,则更不应延续。为此,我们邀国外出版同行、国内技术专家一道,踏上朝花夕拾之路,竭力为广大读者筛选出历久弥新、震撼依旧的Jolt图书精品。

Jolt获奖图书皆由业界专家一致评出,并得到软件从业人员的高度认可,虽然这些书今天读来,不再能看到上世纪史诗时代那般日新月异的理论突破,以及依赖于高深繁复的科学研究所取得的系统化成果,更多是在日复一日的开发实践中总结和提炼出来的工程思想和方法论。重新选材之所以有所弃取,从Jolt多年来的评奖规律中可窥端倪——

一万小时真理见

凡是在工程思想领域取得革命性、颠覆性突破的图书,就被归于震撼获奖分类。比如,从基于过程的程序设计模型过渡到面向对象的全新模型,就是软件开发思想上的一次带来巨大震撼的革命;再比如,打破传统的瀑布模型而转向持续集成的软件交付模型,这也是一场业界的重大思想转变。像这样的重大思想突破,可以说是数年甚至数十年一遇的,而荣获Jolt大奖的图书中更为常见的,则是基于最佳实践的生产效率获奖者。获得此类殊荣的图书,都是作者们从平凡的、重复的,甚至用一般人的眼光看来不怎么起眼的日常开发实践中,以独具的慧眼、过人的耐心和大胆的创新,闯开一条不平常道路的心血与经验总结。

这些图书所涉及的主题,都是普通的软件开发人员每天要面对的工作——代码阅读、撰写测试用例、修复软件问题……但就是这样貌似平淡无奇的工作,是否能每一天、每一个项目都做好,着实拉开了软件开发人员素质的差距,也决定了软件企业开发出来的产品和服务的质量。我们中国有一句古话,叫做熟能生巧;某位著名企业家也说过一句家喻户晓的名言:把简单的事千百万次地做好,就是不简单的。这些朴素而实际的真理,同样也是本套丛书最能彰显的所谓程序员精神。它建立在脚踏实地的实践基础之上,也充满了对于自由和创新的向往。

名作可堪比名曲

就不因岁月流逝而褪色来说,与这些Jolt名作相媲美者,只有那些百年响彻、震撼古今的经典名曲。希望本丛书带给大家的每部著作,也如百听不厌的乐曲,掩卷良久方余音绕梁,真知存心。仔细想来,软件开发与古典音乐岂非有异曲同工之妙?既是人类心智索问精确科学的探究,亦是寻觅美学享受的追求。工程是艺术的根基,而艺术是工程的极致。衷心地希望各位读者能够认真阅读本丛书的本本珍品,并切实地用于自己的日常工作中,在充分享受大师魅力的同时,为中国的软件事业谱写更多、更震撼的乐章。

 

电子工业出版社博文视点

一二年春 

原作者中文版序

中国是首个将我的开源视角系列作品再版的国家。这可能有很多原因,而其中一个特别吸引人的原因与孔子的著作有关,他在《论语》中广泛地强调了学习研究的重要性。回顾之前撰写《代码阅读》和《代码质量》1 的历程,我了解到,实际上,我鼓励了那些从事开发工作的同事和学生借助研究学习软件代码来提升他们自身的知识和技能,这正是我遵循孔子金玉良言的一种方式。

《代码阅读》一书阐述了开发者应当如何阅读已有的代码。关于为何要进行代码阅读,不少人也给出了许多现实中的原因:修正问题、添加特性、寻找有用的片段,或者作为你所在机构质量控制流程的一部分对其进行复查。然而,进行代码阅读最重要的原因其实是从中学习。从已有的高质量代码中,我们可以学习如何将严谨的代码风格应用于实践,如何编写有用的注释,如何编排代码以方便他人读懂,如何选择有意义的标识符,以及如何将复杂的代码组织为可管控的部分。另外,通过研究代码,我们还可以学习到新的算法、API及架构。简而言之,阅读代码可以帮助我们成为更加优秀的程序员。

《代码质量》一书,退后一步,方为大观。当代码被组装为程序时将会产生所谓的聚现属性2 :可靠性、安全性、CPU利用率、空间占用率、可移植性及可维护性。尽管这些属性可能看起来抽象且难以约束,但是诸多成功的经验表明,借助研究专家级的代码,我们可以学习到许多极好的提升代码质量的方法。我们可以从中发现严谨的错误测试、安全证书的保守处理、高效的算法、灵巧的抽象技术及应用于实践中的基本设计模式。简而言之,这将有助于我们成为极其优秀的程序员。

伟大的哲学家孔子曾经提过:学而不思则罔,思而不学则殆。因此我推荐大家主动花些时间来研究已有的代码并从中予以学习。

Diomidis Spinellis

20119月于雅典

今年恰逢十二五开局之年,在全球软件技术和产业格局孕育重大调整之际,我国软件产业也在工业化、信息化两化融合的大背景下迎来了又一个快速发展的新阶段,这其中机遇与挑战并存。当下软件和信息服务业市场的规模不断扩大,物联网作为又一个万亿元级别的产业将产生千亿元级别的服务外包。预计到2020年,全球潜在的服务外包市场需求将达到1.651.8万亿美元,大力发展软件业及信息服务业将成为各国抓住新机遇、全面深度参与全球化、提升软件产业技术力量的重要途径。目前我国软件产业规模虽已过万亿元,但在核心技术、基础软件等方面仍有很大发展空间。

高素质人才的储备是推进产业健康快速发展的根本保证。高端软件人才的大量持续涌现,关键在于教育,这其中高校无疑要发挥重要的作用。我们高校软件教育者既要继续贯彻党的教育理念,进一步深化我国高级软件人才培养体系的发展进程;同时又要看到我国与欧美等高水平软件人才教育国家之间的距离,师夷长技,以求在全球化浪潮中谋得一席之地。作为一位优秀的软件教育者,Diomidis Spinellis教授的某些理念无疑是非常值得我们学习和借鉴的。他以人类学习自然语言的认知规律为出发点,独辟新径,强调借助代码阅读来提高编程能力。目前这一思潮也已逐渐由欧美向我国渗透。

代码阅读是每一个软件从业人员经常进行的活动,其重要性对于每一个开发者不言而喻,但人们更多的是在本着修改前人代码而进行此项活动的,换言之,仅仅使用了代码阅读的工作属性,而未见开发其学习属性。其实,代码阅读还帮助人们完成了观察模仿创造这样一个过程的初始阶段。南朝刘勰《文心雕龙》里讲观千剑而后识器,与之类似,清乾隆年间蘅塘退士还说熟读唐诗三百首,不会作诗也会吟,这都是强调了观察对于之后创造的重要性(巧合的是写诗和写代码的观察都是借助阅读来完成的)。

令人遗憾的是,一直以来,许多人都认为阅读代码不是件容易的事情,不仅不容易,很多时候还非常枯燥;即使是自己写的代码,有时隔一段时间再回顾也会不知所云。很多人在自己的编码生涯中都或多或少有过一些阅读代码的经历,有自己的一些方法,但也仅仅是一些个人实践而已,缺乏对整体的把握,经常是只见树木不见森林(很多时候仅仅能看到一小部分树木)。为了在学习的过程中少走一些弯路,业界代码阅读与质量提升方面的开宗明义之作——Diomidis Spinellis教授所撰写的两部经典之作《代码阅读》和《代码质量》无疑是推荐给每位从业人员的理想读物。这两部曾荣获美国Jolt软件开发震撼大奖的作品,影响了一代程序员,是相关领域中的经典名作。

我阅读了两部书的译稿,并非常欣喜地将它们推荐给每一位读者。该书译者和编辑们严谨、认真的工作使得本版最大程度地还原了作者的原意,相信经由他们的辛勤努力,必将能为广大读者献上一道惊艳的佳作。

欧阳修说:立身以立学为先,立学以读书为本。衷心希望广大读者借由本书立学解惑,提升自我。

 

李战怀

2011岁末

软件产业无疑是本世纪最具广阔前景的新兴产业之一。在该领域,中国拥有数以千亿计的市场规模,更有无数富有才华和热情的年轻从业者。作为一种无污染、微能耗、高产值且劳动力密集的产业,软件产业不但能大幅度提高国家整体经济运行效率,而且自身也可以形成庞大规模,拉升国民经济总体水平。进入新世纪以来,中国的软件产业迅猛发展,成果固然可喜,但我们也应清醒地看到中国软件产业同美国、日本、印度等软件强国之间的差距。随着信息技术的发展,软件产业将会成为衡量一个国家综合国力的标志之一。因此,发展和扶持软件产业,是一个国家提高国家竞争力的重要途径,也是参与全球化竞争所必须占领的战略制高点。为鼓励和促进软件产业的健康、可持续发展,国务院先后印发了《鼓励软件产业和集成电路产业发展的若干政策》和《进一步鼓励软件产业和集成电路产业发展若干政策》两项通知,以及《国务院振兴软件产业行动纲要》等纲领性、指导性文件,力求在产业政策、人才培养及其他相关配套等各方面形成合力,共促中国软件产业大发展。

作为推进中国软件产业大发展战略布局中的重要一环,软件人才培养始终是万业之基、重中之重。为此,教育部于2001年在全国顶尖高校中组织成立了35所国家示范软件学院。国家示范软件学院本着开拓创新、改革示范、育人为本、质量为先、面向产业、走向世界的理念,以培养国际化、工程型人才为目标,在软件人才培养的大路上披荆斩棘、一路求索,先后为国家输送了十万余名高素质软件人才,摸索出了一整套行之有效的软件人才培养体系,为中国软件产业发展提供了源源不断的动力。

时光荏苒,2011年,国家示范软件学院成立10周年庆祝大会在北京隆重举行。包括国家部委领导、教育界和软件企业界的耕耘者们、外国专家学者在内的业界精英济济一堂,共同庆祝这承载了无数光荣与梦想的历史时刻。作为国家示范软件学院的优秀毕业生代表,我也有幸参加了此次盛会。会上有关领导及专家充分肯定了国家示范软件学院的办学成果,同时寄予中国软件产业和软件教育事业以厚望。

科教可以兴国,中国软件产业的可持续发展,关键在于人才。要培养出更多高质量、国际化、工程型的软件人才,高校与社会同样承担有艰巨而光荣的使命。我们既要继续发扬和巩固已经取得的成果,推进中国软件人才培养的科学化体系建设,同时也应看到中国同世界上其他软件强国在软件教育方面的差距,取人之长、以补己短。作为当今世界上的头号科技强国和世界软件产业的领跑者,美国在软件人才培养方面同样有许多可贵的经验和成果值得我们借鉴。其中,关于软件技术的许多真知灼见已经凝聚成了一本本经久不衰的科技专著。作为一名技术作家、译者,我们一直希望同中国的IT出版业一道将这些承载着先进思想的著作介绍给国人,若能以这种方式为中国软件产业和软件教育事业略尽绵薄之力,吾辈也必将倍感欢欣。

由国际知名的Addison-Wesley出版社推出的高效软件开发系列丛书为现代软件开发的方方面面提供了专业的建议和意见。收录在该系列中的书籍本本都是技术方面声名卓著的佳作,这些书籍的作者在创作时煞费苦心,力求作品篇幅适中、易于阅读,同时保证作品的价值能够历久弥新,而不会随时光的流逝而渐显黯淡。系列中的每一本都描述了一项软件开发的核心话题,这些内容可能是业界专家们在开发中始终秉承的,也可能是需要本书的读者们引以为戒的,而之所以这样做的目的只有一个,就是要创造出最杰出的软件。Diomidis Spinellis教授的两部著作《代码质量》和《代码阅读》均收录在此系列中。在IT产业蓬勃发展的今日,电子工业出版社顺应时代发展和广大读者希冀,隆重推出了“Jolt大奖精选丛书,《代码阅读》和《代码质量》再次被收入其中。经典之作《代码质量》得以拨云开雾,同广大中国读者见面。

作者的这两本书都可谓是在业界独树一帜的经典之作。在学习如何编写代码之前,应当首先学习如何阅读代码,因为众所周知,学习其他语言方法都是先学阅读,再学写作,而且在当前,大多数开发人员的主要任务是修改已存在的代码,而不是开发代码。即使是主要从事开发的人员,也难免出于各种各样的目的去阅读他人的代码,其中一件重要的事情就是优化代码,提升代码质量。如果说作者的《代码阅读》一书将阅读代码的技巧传授给了读者,那么《代码质量》则为后续的提高和优化提供了指导。《代码质量》一书重点讨论了代码的非功能特性,深入讲述代码如何满足重要的非功能性需求,如可靠性、安全性、可移植性和可维护性,以及时间效率和空间效率。本书从Apache Web应用服务器、BSD/UNIX操作系统和HSQLDB数据库等开源项目中攫取数百个小例子,并以实例为基准点,辅以理论分析,从实用的角度讲述每个专业软件开发人员能立即运用的概念和技术。我们相信,每一名软件从业者都会从中有所收获、有所提高。作为译者,我们真诚地将该书推荐给国内的读者,希望借由本书,可以提高广大软件从业人员的编码质量,打开中国软件人才培养的新思路,若能如此,我心足矣。

怀揣着这样的心情,我们始终秉持着一丝不苟的态度,力求把经典之作原汁原味地带给中国读者。而一本专业技术领域的译作得以成功问世,其中之波折也是在所难免。幸得多位业内专家不吝指导,使得我们的工作倍感鼓舞。在本书翻译之初,原书作者Diomidis Spinellis教授即给我们提出了许多宝贵的建议,他的指导给予我们极大的帮助。此外,西北工业大学博士生导师、计算机学院副院长李战怀教授审阅了译稿,并欣然为本书作序推荐,感激之情,溢于言表。参与本书翻译校对工作的还有杨宁、丁玮、张曼、王团团、胡宗正、王延青,对于他们严肃认真的工作态度,谨表示由衷敬佩。

最高品质的图书始终是作译者永恒的追求。但有一千个读者,就有一千个哈姆雷特。因此,我们也真诚地希望本书的读者能够把阅读中的所想所得与我们分享,能够把书中的纰漏毫无保留地向我们指出,从而使得本书能够日臻完善,以利来者。欢迎访问笔者的技术博客http://baimafujinji.blog.51cto.com,或发邮件至heisbaixingzhi@163.com

 

左飞

2011年秋

原书序言

鲜有作者能够在计算机领域中开辟全新的主题,但Diomidis Spinellis却借其第一本书《代码阅读》做到了这一点。目前大多数IT从业人员对研究阅读代码而非编写代码的书籍异常渴望。软件思想的一个学派强调应在教授初学者编写代码前先教授他们阅读代码。原因在于:一,先读后写是其他自然语言常用的学习办法;二,21世纪以来大多数程序员的任务为修改现有代码(这就是说应先阅读),而非开发新的代码。故而我十分欣喜地发现Spinellis认识到了代码阅读的重要性,并就该主题为我们撰写了一本字字珠玑的图书。

当然,这也引发了一个有趣的问题:Spinellis重装上阵时会为我们带来什么?他能多少次开辟全新的主题?其实,遗憾的是他的新书《代码质量》并未做这方面的尝试,但他却在其中分析了在我看来最为重要和令人费解的软件工程主题——软件质量。说其重要是因为代码若无足够的质量保证可能就完全没有价值;说其费解是因为软件质量在一千个软件开发者眼中就有一千个标准,让人难以捉摸。

Spinellis非常出色地阐述了这一重要而令人费解的主题。当前,大多数关于软件质量的讨论都是专注于高级管理层面的,身处在这样一个环境中,Spinellis却剖析事物本质,直击代码质量中所反映的至关重要的主题——质量技术。在我自己看来(可能非常偏激),于管理层面上讨论代码质量几乎是毫无意义的,原因在于人们只有在代码实现层面上才能看清构成质量的各个元素。以Spinellis所讨论的可维护性与可移植性这两个质量特征为例,若无对代码的分析,我们就不可能理解什么是可维护和可移植的软件。

对那些追求跨过技术层直接进入管理层的读者来说——我认为这类人现在不在少数——这本书并不适于他们理解代码质量。但对于那些认为代码质量本质上首先是一个技术性主题,其次才是一个管理学主题的读者,这本书正是一个合适的起点。作者Spinellis也在本书的开始这样写道:从这本书中,你将学到如何判断软件质量。向他致敬!

Robert L.Glass

20061

 

编程和其他事情一样,错误即重生。

——Alan J. Perlis

我希望自己能在前言中这样写道:你手边的这本书是精心策划而成的出版结果,其始于《代码阅读》,而终于《代码质量》。然而,这么做是对事实的一种歪曲,也是对我们这些工程师所喜爱的有序世界的一种媚从。其实真相是《代码质量》大体上源自一系列的偶然事件。

签下出版《代码阅读》一书的合同时,我手头已有了一个纲要和一些完成的章节。基于这些完成章节的长度和所耗精力,我天真错误地估计了全书长度和成书日程。若你靠编写软件为生,可能已猜到,在书稿应完成之时,我仅仅完成了大纲章节的一半多一点,并已用完所有分拨篇幅。为体面脱身,我建议编辑将已完成的部分(除去关于可移植性的一章)作为《代码阅读》的第1卷出版,而将剩下的工作作为第2卷。我们达成了一致,于是《代码阅读》[Spi03a]出版。结果该书好评如潮,并获得了2004年度软件开发杂志生产效率大奖(The 2004 Software Development Magazine Productivity Awards),还被译为其他6种语言。

在《代码阅读》一书中,我以取自实际工作中的开源项目为例,尝试覆盖软件开发人员可能面对的大多数与代码相关的概念,包括程序结构、数据类型、数据结构、控制流、项目组织、编码标准、文档及架构。在第2卷中,我先前计划涉及接口与面向应用的代码,包括国际化与可移植性问题、常用库与操作系统要素、底层代码、领域特定语言与声明性语言、脚本语言及混合语言系统。但当程序员们拿到《代码阅读》一书后,我从他们那里获得了一些有益的看法。他们给我的反馈表明许多读者都在焦急地等待着下一卷,不过他们期望的并不是一个对于设备驱动程序的详细剖析(我之前为接下来的一卷保留的一章)。20037月,时任编辑Mike Hendrickson建议我撰写一本名为Secure Code Reading的书。虽然作为一个科学家,IT安全是个让我感兴趣的领域,但是我懒于追逐写作安全方面书籍的时尚,故而仅写了一个与安全有关的章节。在有了一个与可移植性相关的章节和一个与安全相关的章节后,我突然找到了新书的书名和主题——《代码质量》,其应将重点放于阅读和编写软件代码及软件代码的质量属性上(这些也时常被称为非功能属性)。

借助阅读软件系统的代码,我们可以发现非功能属性与产品的非功能需求相关。该需求不与系统提供的特定功能直接相关,而是与更广泛的重要系统属性有一定的关系。常见的与系统属性相关的非功能属性为:可靠性、可移植性、易用性、互用性、适应性、相依性及可维护性。另外两个与系统的效率有关的值得注意的非功能属性是:系统与时间约束有关的性能和系统空间需求。

借助阅读代码来了解系统非功能属性是开发者必不可少的一项技能,这里面有两个原因。一是未满足非功能需求所引发的结果可能是危险的,甚至是灾难性的。一个系统若仅某些功能需求出现错误(大多数软件系统都含有这样的错误),则其还能以降级模式运行,用户也会被告知避免使用其中的一些功能。但若非功能需求出现错误则较为严重:不安全的Web服务器或不可靠的防抱死系统(ABS)比无法使用更糟糕。二是非功能需求有时难于验证。我们写不出这样一个测试用例使得它能够验证系统的可靠性或系统是否存在安全漏洞。基于以上两点,我们在应对非功能需求及相关软件属性时,需利用我们所能利用的一切手段。将代码与非功能属性联系起来的能力应是每一位软件工程师都需具备的有力武器。

除去视角不同外,《代码质量》沿用了《代码阅读》的成功秘诀,即重点关注已有代码的阅读;仅分析取自现有开源系统的实例;给出所有实例的来源;采用注释来剖析代码;提供有意义的练习来强化读者的批判能力和技巧;在页边空白处指出编码习惯及陷阱;以箴言录的形式总结每章的建议;在延伸阅读部分,给之前的实践以理论的拓展;采用了UMLUnified Modeling Language)来绘制所有简图。其中最为精妙的元素就是我自行强加的规则——避免使用玩具示例(toy example),所有的用例都须取自现有开源项目。为践行该规则,我常常花费数小时来寻找一个合适的例子,它应当既能表明我欲展示的概念,又要易于理解,并且还需足够短小以便可放至一本书中。个人认为这样的活动既有助于智力同时也可使作品更为严谨。通常在寻找某方面的缺点时,我会碰到其他值得讨论的内容。有时我对于某理论概念示例的寻找没有什么结果,那么在此情形下,我就有理由认为在实践中这些概念并未重要到必须将其囊括于本书的地步。

《代码质量》和《代码阅读》背后的原理与动机是一脉相承的——阅读代码可能是计算机专业人员最常见的活动之一,不过其却较少被当做一门课程来教授或被正式当做一种学习程序设计和编码的方法来使用。所幸开源软件的盛行已向人们提供了大量可供自由阅读和学习的代码。基于开源软件的初级读本是提高个人编程能力的有价值的工具。所以我希望这两本书能够引发人们在计算机教育课程中加入代码阅读的课程、活动及练习的热情。如此一来,在不久的将来,就像是从优秀的文学作品中学习写作一般,我们的学生就可从现有的开源系统中学习编程了。

内容及补充材料

我决定在《代码质量》中沿用同《代码阅读》中一样的系统和分发版本作为开源代码样例。原因是我认为两卷间必须存在一定的延续性,这样方可允许读者观察到同样的源代码如何既能在《代码阅读》中被用于认识其所涵盖的功能、架构及设计等功能特性,又能被用于《代码质量》中所涉及的非功能特性。

本书所用的代码来自于那些大多数仅有历史意义的代码快照。然而有了它们,我就可以借机展示那些在较新版本中已被发现并更正的安全漏洞、同步问题、可移植性问题、API调用的误用及其他bug。这些许久之前的代码可能表明,其作者目前或已晋升至管理岗位以致不喜于阅读此类书籍,或视力退化至无法看清书中字体。我可借助这些变化来自由品评相关代码,而不用担心险恶的报复。虽如此,我还是可能被指责诽谤那些怀有推进开源运动信念的作者所贡献之代码,我对此表示理解,同时也认同我们应当改进这些代码而非仅仅对其评论。若我的评论使某些源代码的作者感到冒犯,请允许我预先在这里真诚的道歉。不过作为对自己言行的辩护,我个人认为在大多数实例中,我的评论并非针对特定的代码片段而是借助其来指明开发者在实践中应避免的行为。通常情况下,被我作为反例来使用的代码都是易受此类伤害的对象,原因在于它们被编写时,技术及其他方面的限制使得其也算是一种合理的行为,抑或我的一些特定的批评其实脱离了代码所处上下文。无论何种情形,我都希望这些评论能得到你的善意一笑,同时我也坦率承认,我本人的代码也包含有类似的,甚至可能更糟的错误。

选择本书示例所用系统时,出于实用的原因,我希望这些代码能够适于作为教学载体。故而我所寻找的内容涵盖代码质量、结构、设计、功用、流行度及无版权问题。我尽可能平衡语言的选择,积极寻找合适的JavaC++代码。然而,当类似概念可被多种语言演示时,我选择采用C作为最小公分母。所以,本书中引用的61%的代码为C代码,这其中包括适用所有语言的实例及系统级编程(多用C语言)实例。另外19%的实例引用了Java代码。选择Java代码是为了展示与面向对象概念及相关API有关的元素。这些概念多也适用于C#,并且许多也适用于C++4%的实例引用了C++代码)。

此外,在本书中我也更为强调UNIX中的API和工具而非Windows中的类似元素。原因依然涉及最小公分母逻辑——许多UNIX中的工具和APIWindows中也有对应元素,而反之则不然。大量与UNIX相兼容的系统,如GNU/LinuxBSD的一些变体,都是免费提供的,并且常常还向使用者提供可引导的CD-ROM,如此一来任何人都可轻松体验这样的系统。最后,UNIX中的API和工具,从我所引示例的细节方面来看,在过去30余年来一直保持着惊人的稳定,这就为我们讨论和展示通用规则提供了一个坚实的根基。虽然如此,我还是在一些地方引用了Windows API及命令来讨论非UNIX平台上的情况。但是请别让这些引用欺骗了你——我并不认为本书涉及Windows平台的编程问题要比涉及UNIX平台的相关问题完整和广泛。

除去在所有实例中均采用开源软件外,本书可能被(狭隘地)指为忽略了大量的流行时尚,如JavaC#WindowsLinux及以解决现有问题为导向的写作风格。其实我很看重前述这些元素:我这里29%的机器运行着Linux,我教授一门Java课程,我已在Windows平台上写了大量程序,并且我的书架上至少有10本书充满了带序号的段落来提供解决很多问题的具体建议。然而,我还是相信在当今这个充满变化的世界,理解说教背后的原理至关重要。正如你在下面的章节中将会看到的,一旦我们将重点关注于原理,则:

    底层技术的选择常常会被虚化。

    我们学到的东西就拥有更广的应用性和更长的时效。

    具体的建议会自行浮现(见每一章末的那些值得记住的建议)。

最重要的一点在于,是否理解编程讲义背后的原理才是一个受尊重的软件工程师同一个卑微的码农之间的区别。

鸣谢

许多人都慷慨贡献了他们的建议、评注及时间来助我完成本书的写作。首先,我要感谢本系列的编辑Scott Myers,他担起了类似于读者的角色,巧妙地为本书指引了方向,系统地阐明了本书可在哪些地方变得更加易读和精悍。Hal FultonHang LauGabor Liptak阅读了本书的样章并提出了许多有用的评注和意见。Chris CarpenterRobert L. Glass也审阅了本书的样章及之后的整个草稿,他们的智慧和经验让我受益匪浅。十分感谢Konstantinos AboudolasDamianos ChatziantoniouGiorgos GousiosVassilios KarakoidasPaul KingSpyros OikonomopoulosColin PercivalVassilis PrevelakisVassilis VlachosGiorgos Zervas,特别是Panagiotis Louridas,他们非正式地审阅了本书早期草稿的部分章节,并提供了用以改进的具体评注和建议。此外,Stephanos Androutsellis-TheotokisLefteris AngelisDavide P. CervoneGiorgos GiaglisStavros GrigorakakisFred GrottChris F. KemererSpyros KokolakisAlexandros KouloumbisIsidor KouvelasTim LittlefairApostolis MalatrasNancy PouloudiAngeliki PoulymenakouYiannis SamoladasGiorgos SarkosDag-Erling SmørgravIoannis StamelosDave ThomasYar TikhiyGreg WilsonTakuya YamashitaAlexios ZavrasGiorgos Zouganelis也提供了有价值的建议,我时常突然跑到他们那里请教一些专业领域中艰涩的问题。我还想感谢雅典经济与贸易大学管理科学与技术系的同事们对于我工作的支持。我也感谢下面这三人在本书写作过程中一些必不可少的方面给予我的指导:Mireille Ducassé(技术写作——1990)、John Ioannidis(代码风格——1983)和Jan-Simon Pendry(时间与空间性能——1988)。

Addison-Wesley出版社的编辑Peter Gordon熟练地指导了本书的创作,处理了很多棘手的问题。Kim Boedigheimer以出众的效率应对了日常事务。7小时的时差常常允许我们在一天内以20个小时一轮换的方式来开展本书的工作。

在本书的创作过程中,Elizabeth Ryan如同一个专业指挥家一般,高效地协调和团结着我们这个全球化多学科的写作团队。

其他作者将本书的文字编辑Evelyn Pyle描述为如同鹰一般目光犀利。我同意该说法并且还要说一句:她的工作真是十分出色,她找出了很多我永远也想象不出的手稿错误,同时她也比较注意细节和一致性,并据此对相关文字做了修改,仅有少数顶尖程序员才能与之相比。另外两位具有类似编程技艺的人员也为本书的完成做出了自己的贡献:Clovis L. Tondo依靠对书中工具的深入理解和书中代码的惊人尊重完成了本书的排版工作,Sean Davey则依靠自身卓越的能力确保本书风格的一致。

本书绝大多数所引实例来自于已有的开源项目。使用源自真实世界的代码允许我能展示人们在实践中可能遇到的代码类型,而非被简化过的玩具程序。所以我想感谢所有对本书中所引开源程序有贡献的开发者,感谢他们向编程社区贡献其工作成果。若书中代码的作者姓名被列于相应的源代码文件中,则其也会出现于本书的附录中。

 

  

表目录

图目录

原书序言

前言

1  导论       1

1.1  软件质量      1

1.1.1  用户、制造者和管理者眼中的质量       2

1.1.2  质量属性   4

1.1.3  紧张的世界       6

1.2  如何阅读本书      8

1.2.1  排版约定   8

1.2.2  图示   9

1.2.3  图表   11

1.2.4  汇编代码   11

1.2.5  练习   11

1.2.6  补充材料   12

1.2.7  工具   12

2  可靠性    15

2.1  输入问题      16

2.2  输出问题      19

2.2.1  不完整输出或输出缺失   19

2.2.2  错误时刻的正确结果       22

2.2.3  错误的格式       22

2.3  逻辑问题      24

2.3.1  偏差为一的错误与循环迭代   24

2.3.2  被忽视的极端情况   25

2.3.3  被遗漏的情况、条件测试或步骤   27

2.3.4  被遗漏的方法   32

2.3.5  多余的功能       35

2.3.6  误解   37

2.4  计算问题      39

2.4.1  不正确的算法或计算       39

2.4.2  表达式中错误的操作数   41

2.4.3  表达式中不正确的运算符       44

2.4.4  运算符优先级问题   45

2.4.5  溢出、下溢和符号转换错误   46

2.5  并行性与时序问题      48

2.6  接口问题      53

2.6.1  不正确的例程或参数       53

2.6.2  没有测试返回值       55

2.6.3  未做错误探查或恢复       58

2.6.4  资源泄漏   60

2.6.5  面向对象功能的误用       63

2.7  数据处理问题      64

2.7.1  不正确的数据初始化       64

2.7.2  引用错误的数据变量       66

2.7.3  越界引用   69

2.7.4  不正确的下标使用   72

2.7.5  不正确的比例或数据单位       73

2.7.6  错误的数据打包与解包   75

2.7.7  不一致的数据   77

2.8  容错      79

2.8.1  管理策略   79

2.8.2  空间冗余   81

2.8.3  时间冗余   83

2.8.4  可复原性   84

3  安全性    93

3.1  脆弱代码      94

3.2  缓冲区溢出   98

3.3  竞态条件      103

3.4  问题API       106

3.4.1  容易出现缓冲区溢出的函数   106

3.4.2  格式字符串漏洞       108

3.4.3  路径和命令行解释器元字符漏洞   110

3.4.4  临时文件   111

3.4.5  不适合做加密用途的函数       112

3.4.6  可篡改数据       114

3.5  不可信输入   115

3.6  结果验证      120

3.7  数据与特权泄漏   124

3.7.1  数据泄漏   124

3.7.2  特权泄漏   128

3.7.3  Java的方案       129

3.7.4  分离特权代码   131

3.8  特洛伊木马   133

3.9  工具      135

4  时间性能       139

4.1  测量技术      143

4.1.1  负载描述   144

4.1.2  受限于I/O的任务    145

4.1.3  受限于内核的任务   148

4.1.4  受限于CPU的任务和剖析工具      149

4.2  算法复杂性   158

4.3  独立的代码   163

4.4  与操作系统交互   167

4.5  与外设交互   173

4.6  非故意的交互      175

4.7  缓存      178

4.7.1  一个简单的系统调用缓存       178

4.7.2  替换策略   180

4.7.3  预先计算结果   182

5  空间性能       189

5.1  数据      190

5.1.1  基本数据类型   191

5.1.2  聚合数据类型   194

5.1.3  对齐   196

5.1.4  对象   202

5.2  内存组织      206

5.3  内存层级结构      210

5.3.1  主存及其高速缓存   211

5.3.2  磁盘缓存和后备存储器   214

5.3.3  交换区和基于文件的磁盘存储       216

5.4  进程/操作系统接口   217

5.4.1  内存分配   218

5.4.2  内存映射   219

5.4.3  数据映射   219

5.4.4  代码映射   220

5.4.5  访问硬件资源   221

5.4.6  进程间通信       222

5.5  堆内存管理   224

5.5.1  堆碎片       225

5.5.2  堆剖析       230

5.5.3  内存泄漏   233

5.5.4  垃圾回收   237

5.6  栈内存管理   239

5.6.1  栈帧   240

5.6.2  栈空间       243

5.7  代码      248

5.7.1  设计期       250

5.7.2  编码期       252

5.7.3  构建期       253

6  可移植性       261

6.1  操作系统      262

6.2  硬件与处理器架构      267

6.2.1  数据类型的属性       267

6.2.2  数据存储   269

6.2.3  特定于机器的代码   271

6.3  编译器与语言扩展      273

6.3.1  编译器错误       273

6.4  图形用户界面(GUI       277

6.5  国际化与本地化   279

6.5.1  字符集       280

6.5.2  区域   282

6.5.3  消息   285

7  可维护性       293

7.1  测量可维护性      294

7.1.1  可维护性指数   294

7.1.2  面向对象程序的度量       300

7.1.3  包的相关性度量       309

7.2  可分析性      316

7.2.1  一致性       318

7.2.2  表达式格式化   319

7.2.3  语句格式化       320

7.2.4  命名惯例   321

7.2.5  语句级注释       324

7.2.6  版本注释   326

7.2.7  视觉结构:块与缩进       327

7.2.8  表达式、函数以及方法的长度       328

7.2.9  控制结构   331

7.2.10  布尔表达式     335

7.2.11  可辨认性与内聚性  337

7.2.12  依赖和耦合     339

7.2.13  代码块注释     351

7.2.14  数据声明注释  354

7.2.15  恰当的标识符名字  355

7.2.16  依赖的位置     356

7.2.17  不确定性  357

7.2.18  可复查性  358

7.3  可变性   363

7.3.1  识别   363

7.3.2  分离   368

7.4  稳定性   377

7.4.1  封装与数据隐藏       378

7.4.2  数据抽象   381

7.4.3  类型检查   383

7.4.4  编译时断言       386

7.4.5  运行时检查和查看时断言       389

7.5  可测试性      390

7.5.1  单元测试   391

7.5.2  集成测试   394

7.5.3  系统测试   396

7.5.4  测试覆盖度分析       398

7.5.5  偶发性测试       401

7.6  开发环境的影响   406

7.6.1  增量构建   407

7.6.2  调整构建性能   410

8  浮点运算       417

8.1  浮点数表示   418

8.1.1  量度误差   420

8.1.2  舍入   421

8.1.3  内存格式   424

8.1.4  规格化和隐含的一位       425

8.1.5  阶码偏移   425

8.1.6  负数   426

8.1.7  反向规格化数   426

8.1.8  特殊值       427

8.2  舍入      428

8.3  溢出      432

8.4  下溢      434

8.5  消去      437

8.6  合并      441

8.7  无效运算      445

附录A  源代码致谢人员名单    453

参考文献       455

 

2-1  测试极端情况  26

2-2  测试一个不含尾端的非对称范围的例子     26

2-3  整数量的范围  46

2-4  在程序生命周期不同阶段所做的参数类型检查  54

2-5  C++中约束类的使用  64

 

3-1  UNIX服务器开放网络端口列表   96

3-2  Windows工作站开放网络端口列表     96

3-3  进行ftpd缓冲区攻击时的栈 100

3-4  展开栈(默认情况)     102

3-5  在攻击过程中展开栈     102

3-6  不安全的C函数及其安全的替代函数 107

3-7  Java 2平台SE 5.0的许可     130

 

4-1  用时剖析特征、诊断工具及解决方案  144

4-2  由环境切换和进程间通信导致的开销  168

4-3  低速外设导致的开销     174

 

5-1  不同架构下基本数据类型的表示  191

5-2  AMD64架构上结构体元素的对齐       197

5-3  一个程序在不同架构和操作系统上的内存布局  209

5-4  连续的地址空间限制及相应的解决方案     216

5-5  构建选项对程序大小的影响  254

 

6-1  各种开发平台的可移植性问题     262

6-2  Java平台标准字符编码  282

6-3  区域特定的字段     284

 

7-1  可维护性指数的参数     295

7-2  WebServerConnection方法与它们使用的字段    306

7-3  WebServerConnection类的相似(内聚)方法与相异方法 307

7-4  不同类型的访问控制下可以访问类成员的方法数     379

 

8-1  不同舍入模型的例子     422

8-2  不同浮点格式的关键属性     424

8-3  导致异常的运算和操作数     445

1-1  软件质量的各个方面的例子:使用中质量、

外部质量、内部质量及过程质量       3

1-2  本书脉络:软件质量的要素  5

1-3  质量特征间的冲突  7

1-4  标注列表示例  9

1-5  基于UML的图示符号   10

 

2-1  使用评注,toString方法的反射式实现       21

2-2  借助策略模式关联代码和数据     30

2-3  使用接口实现创建处理器     30

2-4  对象私有资源的显式管理     34

2-5  邮件传送代理守护程序sendmail的调试代码     37

2-6  通过静态分析探查null指针引用  42

2-7  借助waitnotify管理资源的使用     49

2-8  使用有瑕疵的双检锁模式     51

2-9  内存泄漏与资源泄漏之间的关系  61

2-10  NetBSD内核中分配与释放文件描述符 62

2-11  使用自动产生的源代码定义字体的轮廓    65

2-12  NetBSD源代码中全局变量定义(左)和引用(右)的位置 67

2-13  NetBSD内核全局变量所用的类型     68

2-14  将字节串编码为改进过的UTF-8格式      76

2-15  SCSI设备驱动程序中一个命令序列重传  84

2-16  自有故障的磁盘上恢复数据       87

 

3-1  FTP守护进程中的缓冲区溢出     99

3-2  PPP守护代码中的一个竞态条件  104

3-3  pppd漏洞的检查/使用时间差利用       104

3-4  检查/使用时间差漏洞利用时序图 105

3-5  strcpystrcat的不安全用法 108

3-6  strcpystrcat正确应用在一个动态分配大小的缓冲区上 108

3-7  不安全地清除环境变量  116

3-8  安全地清除环境变量     116

3-9  未正确检查traceroute实现中的错误返回值       122

3-10  TCP Wrappers程序中嵌入特洛伊木马代码   134

 

4-1  专家对于优化代码的警告     142

4-2  HSQLDB代码中EJP对帕累托法则的阐述    151

4-3  对于vfprintf函数的gprof输出的例子 153

4-4  调用图中处理时间的传播     156

4-5  某些常见算法类别的相对性能     159

4-6  简单cat调用中的系统调用   169

4-7  logger调用时本地进程间通信产生的系统调用  171

4-8  ping名字查询的远程DNS进程间通信所产生的系统调用       172

4-9  系统颠簸对运行时性能的影响     176

4-10  使用用户ID来命名缓存代码     179

4-11  缓存数据库行记录       181

 

5-1  结构体的两种存储方式:填充以确保对齐(上),

打包以节省内存(下)       199

5-2  从大到小排列结构体元素,确保对齐且节省内存     200

5-3  OpenCL算法继承树的UML类图 203

5-4  三个OpenCL算法类的C++虚函数表  204

5-5  指向实例数据的Java对象    205

5-6  有着一个指向实例数据和类数据的句柄的Java对象 206

5-7  内存资源的类型     207

5-8  进程内存组织  207

5-9  现代计算机的一个存储层次结构  211

5-10  100万个分配对象的大小分布    227

5-11  100万个分配对象的生命周期分布     227

5-12  用于显示内存碎片的内存池快照       228

5-13  外部内存碎片的一个例子   229

5-14  内部内存碎片的一个例子   229

5-15  Apache HTTP服务器内存分配剖析    231

5-16  sed流编辑器的内存剖析     232

5-17  valgrind内存泄漏测试工具的报告     236

5-18  C正则表达式库中的内存泄漏    237

5-20  一个栈帧的内容   242

5-21  栈大小的快照       244

5-22  数据和栈大小的关系   245

5-23  源文件与目标文件大小之间的关系   249

5-24  ACE地址类型中的继承      251

 

6-1  使用隔离层来提供可移植性  264

6-2  存储于小端与大端架构上的整数0x04030201    270

6-3  GUI可移植性策略的例子     278

6-4  使用catgets接口做消息处理 287

6-5  使用gettext接口做消息处理 287

6-6  使用GNU gettext工具的消息本地化流程   288

6-7  Java servlet中访问本地化消息 290

 

7-1  测量扩展圈复杂度  297

7-2  FreeBSD内核和用户程序的增长与可维护性指标随时间的变化     298

7-3  所有FreeBSD模块的可维护性指数分布     298

7-4  类的加权方法数度量:对所有的HSQLDB类(左);

不同的WMC值对应的Eclipse类个数(右)   301

7-5  继承树深度度量:对于所有的HSQLDB类(左);

不同DIT值对应的Eclipse类的数量(右)      302

7-6  HSQLDB:高DIT值的类的继承树度量和它们所定义的方法数     302

7-7  Eclipse类的子类数度量 303

7-8  对象类之间的耦合:对于每个HSQLDB类(左);

不同的CBO值对应的Eclipse类的数量(右) 304

7-9  类的响应:对于所有的HSQLDB类(左);

不同的CBO值对应的Eclipse类的数量(右) 305

7-10  方法的内聚缺乏度:对于所有的HSQLDB类(左);

对应于不同LCOM值的Eclipse类的个数(右)     308

7-11  org.hsqldb包中的公有类和私有类     310

7-12  Tomcat中一个不稳定的包   311

7-13  Eclipse中一个稳定的包      311

7-14  Eclipse的离心耦合与向心耦合之间的关系      312

7-15  Eclipse包(左)和第三方包(右)的不稳定性分布      313

7-16  junit包的稳定依赖       313

7-17  MX4J包的不太稳定依赖    314

7-18  Eclipse包的不稳定性与抽象度分布   315

7-19  XercesEclipse间的循环依赖  316

7-20  所有的FreeBSD模块可维护性指数与模块大小的关系   329

7-21  65000C函数的函数长度分布 330

7-22  在处理用户响应的过程中缺乏规则性       333

7-23  用于处理用户响应的一个规则的控制结构       333

7-24  在同一代码段里不同的breakcontinue范围  334

7-25  edDES CBC实现中的公共耦合    345

7-26  edDES CBC实现中的公共耦合关系    346

7-27  NetBSD内核中读取并使用磁盘分区内核    349

7-28  低带宽XLBX)扩展中增量缓存的设计       359

7-29  低带宽XLBX)扩展中增量缓存的实现       360

7-30  servlet容器各元素间不必要的关系    370

7-31  使用责任链设计模式来分离类   370

7-32  两个不同的Catalina文件的公共代码行    374

7-33  难以修改的格式化注释(左)与便于修改的格式化注释(右)   377

7-34  在最内部的代码块中声明变量   378

7-35  7UNIX中直接解释目录的数据       383

7-36  Java 1.5之前的代码使用松散类型     384

7-37  Cfomd实现中的特别单元测试     392

7-38  JUnit测试框架中的单元测试     393

7-39  Perl源代码测试覆盖度(左)和分支覆盖度(右)

与测试用例执行数的关系    400

7-40  正则表达式引擎中断言的使用   402

7-41  包含文件依赖关系的简化图       409

7-42  文档依赖       411

 

8-1  二次方程消去误差以及对精度的影响  440

8-2  计算远离原点的三角形面积时的合并误差  442

8-3  验证浮点运算结果的代码     448

posted @ 2012-08-27 18:21  博文视点(北京)官方博客  阅读(727)  评论(0编辑  收藏  举报