All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc. CHINESE SIMPLIFIED language edition published by PEARSON EDUCATION ASIA LTD., and POSTS & TELECOMMUNICATIONS PRESS Copyright © 2009.
本书封面贴有Pearson Education(培生教育出版集团)激光防伪标签。无标签者不得销售。
内容提要
代码整洁之道
软件质量,不但依赖于架构及项目管理,而且与代码质量紧密相关。这一点,无论是敏捷开发流派还是传统开发流派,都不得不承认。
本书提出一种观念:代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。作为编程领域的佼佼者,本书作者给出了一系列行之有效的整洁代码操作实践。这些实践在本书中体现为一条条规则(或称“启示”),并辅以来自现实项目的正、反两面的范例。只要遵循这些规则,就能编写出干净的代码,从而有效提升代码质量。
本书阅读对象为一切有志于改善代码质量的程序员及技术经理。书中介绍的规则均来自作者多年的实践经验,涵盖从命名到重构的多个编程方面,虽为一“家”之言,然诚有可资借鉴的价值。
关于封面
代码整洁之道
封面的图片是M104:草帽星系(The Sombrero Galaxy)。M104坐落于处女座(Virgo),距地球仅3000万光年。其核心是一个质量超大的黑洞,有100万个太阳那么重。
这幅图是否让你想起了Klingon星球(克林贡)[1]的卫星Praxis(普拉西斯)爆炸的事?我清楚地记得,在《星舰迷航 VI》中,大爆炸之后碎片四溅,飞舞出一个赤道光环的场景。至此,光环就成为科幻电影中爆炸场景的必然产物了。甚至就在《星舰迷航》系列电影的后续情节中,Alderaan(阿尔德然)的爆炸也有类似场景出现。
环绕M104的光环是什么造成的?它为何会有如此巨大的膨胀率和如此明亮而微小的内核?在我看来,仿佛那位于中心位置的黑洞勃然大怒,向星系的中心扔出了一个3万光年大的洞一般。在这场宇宙大崩塌所及范围之内的居民全都大难临头了。
超大质量的黑洞以星体为食,将星体的相当部分质量转换为能量。方程式E=MC2已经足够体现杠杆作用了,但当M有一颗星体那么大的质量时,看吧!在那巨兽酒足饭饱之前,有多少星体会一头撞进它的胃里?核心部分空洞的大小,是否说明了些什么呢?
在红外影像中,光环中的热粒子闪耀着穿过了中心膨胀体。这两幅影像组合起来,显现出我们从未见过的景象,展示了久远之前曾熊熊燃烧的火海。
[1] 系列剧《星舰迷航》(Star Trek)中的故事情节,Praxis星爆炸,由此导致联邦和Klingon达成首次和平协议。
序
代码整洁之道
乐嚼(Ga-Jol)是在丹麦最受欢迎的糖果品种之一,它浓郁的甘草味道,完美地弥补了此地潮湿且时常寒冷的天气。对于我们这些丹麦人,乐嚼的妙处还在于包装盒顶上印制的哲言慧语。今早我买了一包两件装,在其包装盒上发现这句丹麦谚语:
Ærlighed i små ting er ikke nogen lille ting.
“小处诚实非小事。”这句话正好是我想在这里说的。以小见大。本书写到了一些价值殊胜的小主题。
神在细节之中,建筑师Ludwig mies van der Rohe(路德维希·密斯·范·德·罗)[1]如是说。这句话引发了有关软件开发、特别是敏捷软件开发中架构所处地位的若干争论。鲍勃(Bob)[2]和我时常发现自己沉湎于此类对话中。没错,Ludwig mies van der Rohe的确专注于效用和基于宏伟架构之上的永恒建筑形式。然而,他也为自己设计的每所房屋挑选每个门把手。为什么?因为小处见大。
就TDD[3]话题展开目前仍在继续的“辩论”时,鲍勃和我认识到,我们均同意软件架构在开发中占据重要地位,但就其确切意义而言,我们之间还有分歧。然而,这种矛与盾孰利的讨论相对而言并不重要,因为在项目开始之时,我们理所当然应该让专业人士投入些许时间去思考及规划。20世纪90年代末期有关仅以测试和代码驱动设计的概念已一去不返。相对于任何宏伟愿景,对细节的关注甚至是更为关键的专业性基础。首先,开发者通过小型实践获得可用于大型实践的技能和信用度。其次,宏大建筑中最细小的部分,比如关不紧的门、有点儿没铺平的地板,甚至是凌乱的桌面,都会将整个大局的魅力毁灭殆尽。这就是整洁代码之所系。
架构只是软件开发用到的借喻之一,主要用在那种等同于建筑师交付毛坯房一般交付初始软件产品的场合。在Scrum和敏捷(Agile)的日子里,人们关注的是快速将产品推向市场。我们要求工厂全速运转、生产软件。这就是人类工厂:懂思考、会感受的编码人,他们由产品备忘或用户故事开始创造产品。来自制造业的借喻在这种场合大行其道。例如,Scrum就从装配线式的日本汽车生产方式中获益良多。
即便是在汽车工业里,大量工作也并不在于生产而在于维护——或避免维护。对于软件而言,百分之八十或更多的工作量集中在我们美其名曰“维护”的事情上:其实就是修修补补。与其接受西方关于制造好软件的传统看法,不如将其看作建筑工业中的房屋修理工,或者汽车领域的汽修工。日本式管理对于这种事怎么说的呢?
大约在1951年,一种名为“全员生产维护”(Total Productive Maintenance,TPM)的质量保证手段在日本出现。它关注维护甚于关注生产。TPM的主要支柱之一是所谓的5S原则体系。5S是一套规程,用“规程”这个词,是为了读者便于理解。5S原则其实是精益(Lean)——西方视野中的一个时髦词,也是在软件领域渐领风骚的时髦词——的基石所在。正如鲍勃大叔(Uncle Bob)在前言中写到的,良好的软件实践遵循这些规程:专注、镇定和思考。这并非总只有关实作,有关推动工厂设备以最高速度运转。5S哲学包括以下概念:
整理(Seiri)[4],或谓组织(想想英语中的sort(分类、排序)一词)。搞清楚事物之所在——通过恰当地命名之类的手段——至关重要。觉得命名标识无关紧要?读读后面的章节吧。
整顿(Seiton),或谓整齐(想想英文中的systematize(系统化)一词)。有句美国老话说:物皆有其位,而后物尽归其位(A place for everything, and everything in its place)。每段代码都该在你希望它所在的地方——如果不在那里,就需要重构了。
清楚(Seiso),或谓清洁(想想英文中的shine(锃亮)一词)。清理工作地的拉线、油污和边角废料。对于那种四处遗弃的带注释的代码及反映过往或期望的无注释代码,本书作者怎么说的来着?除之而后快。
清洁(Seiketsu),或谓标准化。有关如何保持工作地清洁的组内共识。本书有没有提到在开发组内使用一贯的代码风格和实践手段?这些标准从哪里来?读读看。
身美(Shitsuke)[5],或谓纪律(自律)。在实践中贯彻规程,并时时体现于个人工作上,而且要乐于改进。
如果你接受挑战——没错,就是挑战,阅读并应用本书,你就会理解和赞赏上述最后一条。我们最终是在驶向一种负责任的专业精神之根源所在,这种专业性隶属于一个关注产品生命周期的专业领域。在我们遵循TPM来维护机动车和其他机械时,停机维护——等待缺陷显现出来——并不常见。我们更上一层楼:每天检查机械,在磨损机件停止工作之前就换掉它,或者按常例每1000英里(约1609.3km)就更换润滑油、防止磨损和开裂。对于代码,应无情地做重构。还可以更进一步,就像TPM运动在50多年前的创新:一开始就打造更易维护的机械。写出可读的代码,重要程度不亚于写出可执行的代码。1960年左右,围绕TPM引入的终极实践(ultimate practice),关注用全新机械替代旧机械。诚如Fred Brooks所言,我们或许应该每7年就重做一次软件的主要模块,清理缓慢陈腐的代码。也许我们该把重构周期从以年计缩短到以周、以天甚至以小时计。那便是细节所在了。
细节中自有天地,而在生活中应用此类手段时也有微言大义,就像我们一成不变地对那些源自日本的做法寄予厚望一般。这并非只是东方的生活观;英美民间也遍是这类警句。上引“整顿”(Seiton)二字就曾出现在某位俄亥俄州牧师的笔下,他把齐整看作是“荡涤种种罪恶之良方”。“清楚”(Seiso)又如何呢?整洁近乎虔诚(Cleanliness is next to godliness)。一张脏乱的桌子足以夺去一所丽宅的光彩。老话怎么说“身美”(Shitsuke)的?守小节者不亏大节(He who is faithful in little is faithful in much)。对于时时准备在恰当时机做重构,为未来的“大”决定夯实基础,而不是置诸脑后,有什么说法吗?及时一针省九针(A stitch in time saves nine)。早起的鸟儿有虫吃(The early bird catches the worm)。日事日毕(Don’t put off until tomorrow what you can do today)。在精益实践落入软件咨询师之手前,这就是其所谓“最后时机”的本义所在。摆正单项工作在整体中的位置呢?巨木生于树籽(Mighty oaks from little acorns grow)。如何在日常生活中做好简单的防备性工作呢?防病好过治病(An ounce of prevention is worth a pound of cure)。一天一苹果,医生远离我(An apple a day keeps the doctor away)。整洁代码以其对细节的关注,荣耀了深埋于我们现有、或曾有、或该有的壮丽文化之下的智慧根源。
即便是在宏伟的建筑作品中,我们也听到关注细节的回响。想想Ludwig mies van der Rohe的门把手吧。那正是整理(seiri)。认真对待每个变量名。你当用为自己第一个孩子命名般的谨慎来给变量命名。
正如每位房主所知,此类照料和修葺永无休止。建筑师Christopher Alexander——模式与模式语言之父——把每个设计动作看作是较小的局部修复动作。他认为,设计良好结构才是建筑师的本职所在,而更大的建筑形态则当留给模式及居住者搬进的家私来完成。设计始终在持续进行,不只是在新建一个房间时,也在我们重新粉刷墙面、更换旧地毯或者换厨房水槽时。大多数艺术门类也持类似主张。在寻找其他推崇细节的人时,我们发现,19世纪法国作家Gustav Flaubert(古斯塔夫·福楼拜)名列其中。法国诗人Paul Valery(保尔 · 瓦雷里)认为,每首诗歌都无写完之时,得持续重写,直至放弃为止。全心倾注于细节,屡见于追求卓越的行为之中。虽然这无甚新意,但阅读本书对读者仍是一种挑战,你要重拾久已弃置脑后的良好规则,自发自主,“响应改变”。
不幸的是,我们往往见不到人们把对细节的关注当作编程艺术的基础要件。我们过早地放弃了在代码上的工作,并不是因为它业已完成,而是因为我们的价值体系关注外在表现甚于关注要交付之物的本质。疏忽最终结出了恶果:坏东西一再出现。无论是在行业里还是学术领域,研究者都很重视代码的整洁问题。供职于贝尔软件生产研究实验室(Bell Labs Software Production Research)——没错,就是生产!——时,我们有些不太严密的发现,认为前后一致的缩进风格明显标志了较低的缺陷率。我们原指望将质量归因于架构、编程语言或者其他高级概念;我们的专业能力归功于对工具的掌握和各种高高在上的设计方法,至于那些安置于厂区的机器,那些编码者,他们居然通过简单地保持一致缩进风格创造了价值,这简直是一种侮辱。我在17年前就在书中写过,这种风格远不止是一种单纯的能力那么简单。日本式的世界观深知日常工作者的价值,而且,还深知工作者简单的日常行为所锻造的开发系统的价值。质量是上百万次全心投入的结果——而非仅归功于任何来自天堂的伟大方法。这些行为简单却不简陋,也不意味着简易。相反,它们是人力所能达的不仅伟大而且美丽的造物。忽略它们,就不成其为完整的人。
当然,我仍然提倡放宽思路,也推崇根植于深厚领域知识和软件可用性的各种架构手法的价值。但本书与此无关——至少,没有明显关系。本书精妙之处,其意义之深远,不该无人赏识。它正与Peter Sommerlad、Kevlin Henny及Giovanni Asproni等真正写代码的人现今所持的观念相吻合。他们鼓吹“代码即设计”和“简单代码”。我们要谨记,界面就是程序,而且其结构也极大地反映出程序结构,但也理应始终谦逊地承认设计存在于代码中,这至关紧要。制造上的返工导致成本上升,但重做设计却创造出价值。我们应当视代码为设计——作为过程而非终点的设计——这种高尚行为的漂亮体现。耦合与内聚的架构韵律在代码中脉动。Larry Constantine以代码的形式——而不是用UML那种高高在上的抽象概念——来描述耦合与内聚。Richard Garbriel在“Abstraction Descant”(抽象刍议)一文中告诉我们,抽象即恶。代码除恶,而整洁的代码则大抵是圣洁的。
回到我那个小小的乐嚼包装盒,我想要重点提一下,那句丹麦谚语不只是教我们重视小处,更教我们小处要诚实。这意味着对代码诚实、对同僚坦承代码现状,最重要的是在代码问题上不自欺。是否已尽全力“把露营地清理得比来时还干净”?签入代码前是否已做重构?这可不是皮毛小事,它正高卧于敏捷价值的正中位置。Scrum有一种建议的实践,主张重构是“完成”(Done)概念的一部分。无论是架构还是代码都不强求完美,只求竭诚尽力而已。人孰无过,神亦容之(To err is human; to forgive, divine)。在Scrum中,我们使一切可见。我们晾出脏衣服。我们坦承代码状态,因为它永不完美。我们日渐成为完整的人,配得起神的眷顾,也越来越接近细节中的伟大之处。
在自己的专业领域中,我们亟需能得到的一切帮助。假使干净的地板能减少事故发生,假使归置到位的工具能提升生产力,我也会倾力做到。至于本书,在我看过的有关将精益原则应用于软件的印刷品中,是最具实用性的。那班求索者多年来并肩奋斗,不但是为求一己之进步,更将他们的知识通过和你手上正在做的事一般的工作贡献给这个行业。看过鲍勃大叔寄来的原稿之后,我发现,世界竟略有改善了。
对高瞻远瞩的练习业已结束,我要去清理自己的书桌了。
James O. Coplien于丹麦默尔鲁普
[1] 译注:20世纪中期著名现代建筑大师,秉承“少即是多”的建筑设计哲学,缔造了玻璃幕墙等现代建筑结构。
[2] 译注:本书主要作者Robert C. Martin绰号Uncle Bob,这里的“鲍勃”及后文的“鲍勃大叔”就是指Robert C. Martin。
[3] 译注:Test Driven Development,测试驱动开发。
[4] 译注:这些概念最初出现于日本,5个概念的日文罗马字拼音首字母正好都是S,所以这里也保留了日文罗马字拼音写法。中译本以日文汉字直接译出,读者留意,不可直接对应其中文意思。
[5] 译注:中文意为“素养、教养”。
前言
代码整洁之道
习艺之要有二:知和行。你应当习得有关原则、模式和实践的知识,穷尽应知之事,并且要对其了如指掌,通过刻苦实践掌握它。
我可以教你骑自行车的物理学原理。实际上,经典数学的表达方式相对而言确实简洁明了。重力、摩擦力、角动量、质心等,用一页写满方程式的纸就能说明白。有了这些方程式,我可以为你证明出骑车完全可行,而且还可以告诉你骑车所需的全部知识。即便如此,你在初次骑车时还是会跌倒在地。
编码亦同此理。我们可以写下整洁代码的所有“感觉良好”的原则,放手让你去干(换言之,让你从自行车上摔下来)。那样的话,我们算是哪门子老师?而你又会成为怎样的学生呢?
不!本书可不会这么做。
学写整洁代码很难。它可不止于要求你掌握原则和模式。你得在这上面花工夫。你须自行实践,且体验自己的失败。你须观察他人的实践与失败。你须看看别人是怎样蹒跚学步,再转头研究他们的路数。你须看看别人是如何绞尽脑汁做出决策,又是如何为错误决策付出代价。
阅读本书要多用心思。这可不是那种降落前就能读完的“感觉不错”的飞机书。本书要让你用功,而且是非常用功。如何用功?阅读代码——大量代码。而且你要去琢磨某段代码好在什么地方、坏在什么地方。在我们分解,而后组合模块时,你得亦步亦趋地跟上。这得花些工夫,不过值得一试。
本书大致可分为3个部分。前几章介绍编写整洁代码的原则、模式和实践。这部分有相当多的示例代码,读起来颇具挑战性。读完这几章,就为阅读第2部分做好了准备。如果你就此止步,只能祝你好运啦!
第2部分最需要花工夫。这部分包括几个复杂性不断增加的案例研究。每个案例都清理一些代码——把有问题的代码转化为问题少一些的代码。这部分极为详细。你的思维要在讲解和代码段之间跳来跳去。你得分析和理解那些代码,琢磨每次修改的来龙去脉。
你付出的劳动将在第3部分得到回报。这部分只有一章,列出从上述案例研究中得到的启示和灵感。在遍览和清理案例中的代码时,我们把每个操作理由记录为一种启示或灵感。我们尝试去理解自己对阅读和修改代码的反应,尽力了解为什么会有这样的感受、为什么会如此行事。结果得到了一套描述在编写、阅读、清理代码时思维方式的知识库。
如果你在阅读第2部分的案例研究时没有好好用功,那么这套知识库对你来说可能所值无几。在这些案例研究中,每次修改都仔细注明了相关启示的标号。这些标号用方括号标出,如:[H22]。由此你可以看到这些启示在何种环境下被应用和编写。启示本身不值钱,启示与案例研究中清理代码的具体决策之间的关系才有价值。
如果你跳过案例研究部分,只阅读了第1部分和第3部分,那就不过是又看了一本关于写出好软件的“感觉不错”的书。但如果你肯花时间琢磨那些案例,亦步亦趋——站在作者的角度,迫使自己以作者的思维路径考虑问题,就能更深刻地理解这些原则、模式、实践和启示。这样的话,就像一个熟练地掌握了骑车的技术后,自行车就如同其身体的延伸部分那样;对你来说,本书所介绍的整洁代码的原则、模式、实践和启示就成为了本身具有的技艺,而不再是“感觉不错”的知识。
致谢
感谢两位艺术家Jennifer Kohnke和Angela Brooks。Jennifer绘制了每章起始处创意新颖、效果惊人的插图,以及Kent Beck、Ward Cunningham、Bjarne Stroustrup、Ron Jeffries、Grady Booch、Dave Thomas、Michael Feathers和我本人的肖像。
Angela绘制了文中那些精致的插图。这些年她为我画了一些画,包括Agile Software Development: Principles, Patterns, and Practices(中译版《敏捷软件开发:原则、模式与实践》)一书中的大量插图。她是我的长女,常给我带来极大的愉悦。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。
目录
前言
第1章 整洁代码
1.1节要有代码
1.2节糟糕的代码
1.3节混乱的代价
1.4节思想流派
1.5节我们是作者
1.6节童子军军规
1.7节前传与原则
1.8节小结
1.9节文献
第2章 有意义的命名
第3章 函数
第4章 注释
第5章 格式
第6章 对象和数据结构
第7章 错误处理
第8章 边界
第9章 单元测试
第10章 类
第11章 系统
第12章 迭进
第13章 并发编程
13.1节为什么要并发
13.2节挑战
13.3节并发防御原则
13.4节了解Java库
13.5节了解执行模型
13.6节警惕同步方法之间的依赖
13.7节保持同步区域微小
13.8节很难编写正确的关闭代码
13.9节测试线程代码
13.10节小结
13.11节文献
第14章 逐步改进
第15章 JUnit内幕
第16章 重构SerialDate
第17章 味道与启发
附录A 并发编程II
附录B org.jfree.date.SerialDate
结束语
欢迎来到异步社区!