代码整洁之道

978-7-115-52413-3
作者: [美] 罗伯特·C. 马丁(Robert C. Martin)
译者: 韩磊
编辑: 杨海玲

图书目录:

详情

软件质量,不但依赖架构及项目管理,而且与代码质量紧密相关。这一点,无论是敏捷开发流派还是传统开发流派,都不得不承认。 本书提出一种观点:代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。作为编程领域的佼佼者,本书作者给出了一系列行之有效的整洁代码操作实践。这些实践在本书中体现为一条条规则(或称“启示”),并辅以来自实际项目的正、反两面的范例。只要遵循这些规则,就能编写出干净的代码,从而有效提升代码质量。 本书阅读对象为一切有志于改善代码质量的程序员及技术经理。书中介绍的规则均来自作者多年的实践经验,涵盖从命名到重构的多个编程方面,虽为一“家”之言,然诚有可资借鉴的价值。

图书摘要

版权信息

书名:代码整洁之道

ISBN:978-7-115-52413-3

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著    [美] 罗伯特•C. 马丁(Robert C. Martin)

译    韩 磊

责任编辑 杨海玲

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


Authorized translation from the English language edition, entitled CLEAN CODE: A HANDBOOK OF AGILE SOFTWARE CRAFTSMANSHIP, 1st Edition, ISBN: 0132350882 by MARTIN, ROBERT C., published by Pearson Education, Inc, Copyright © 2009 by Pearson Education, Inc.

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 POSTS & TELECOM PRESS, Copyright © 2020.

本书中文简体字版由Pearson Education Inc授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

本书封面贴有Pearson Education(培生教育出版集团)激光防伪标签,无标签者不得销售。

版权所有,侵权必究。


软件质量,不但依赖架构及项目管理,而且与代码质量紧密相关。这一点,无论是敏捷开发流派还是传统开发流派,都不得不承认。

本书提出一种观点:代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。作为编程领域的佼佼者,本书作者给出了一系列行之有效的整洁代码操作实践。这些实践在本书中体现为一条条规则(或称“启示”),并辅以来自实际项目的正、反两面的范例。只要遵循这些规则,就能编写出干净的代码,从而有效提升代码质量。

本书阅读对象为一切有志于改善代码质量的程序员及技术经理。书中介绍的规则均来自作者多年的实践经验,涵盖从命名到重构的多个编程方面,虽为一“家”之言,然诚有可资借鉴的价值。


2007年3月,我在SD West 2007技术大会上聆听了Robert C. Martin(“鲍勃大叔”)的主题演讲“Craftsmanship and the Problem of Productivity: Secrets for Going Fast without Making a Mess”。一身休闲打扮的“鲍勃大叔”,以一曲嘲笑低水平编码者的Code Monkey(代码猴子)开场。

是的,我们就是一群代码猴子,上蹿下跳,自以为领略了编程的真谛。可惜,当我们抓着几个酸桃子,得意扬扬地坐到树枝上,却对自己造成的混乱熟视无睹。那堆“可以运行”的乱麻程序,就在我们的眼皮底下慢慢腐坏。

从听到那场以TDD为主题的演讲之后,我就一直关注“鲍勃大叔”,还有他在TDD和整洁代码方面的言论。2008年,人民邮电出版社(计算机分社)的编辑拿一本书给我看,封面上赫然写着Robert C. Martin的大名。看完原书序和前言,我已经按捺不住,接下了翻译此书的任务。这本书名为Clean Code,乃是Object Mentor(“鲍勃大叔”开办的技术咨询和培训公司)一干大牛在编程方面的经验累积。按“鲍勃大叔”的话来说,就是“Object Mentor整洁代码派”的说明。

正如Coplien在序中所言,宏大建筑中最细小的部分,比如关不紧的门、有点儿没铺平的地板,甚至是凌乱的桌面,都会将整个大局的魅力破坏殆尽。这就是整洁代码之所系。Coplien列举了许多谚语,证明整洁的价值,中国也有修身齐家治国平天下之语。整洁代码的重要性毋庸置疑,问题是如何写出真正整洁的代码。

本书既是整洁代码的定义,亦是如何写出整洁代码的指南。“鲍勃大叔”认为,“写整洁代码,需要遵循大量的小技巧,贯彻刻苦习得的‘整洁感’。这种‘代码感’就是关键所在……它不仅让我们看到代码的优劣,还予我们以借戒规之力化劣为优的攻略。”作者阐述了在命名、函数、注释、代码格式、对象和数据结构、错误处理、边界问题、单元测试、类、系统、并发编程等方面如何做到整洁的经验与最佳实践。长期遵照这些经验编写代码,所谓“代码感”也就自然而然滋生出来。更有价值的部分是“鲍勃大叔”本人对3个Java项目的剖析与改进过程的实操记录。通过这多达3章的重构记录,“鲍勃大叔”充分地证明了童子军军规在编程领域同样适用:离开时要比发现时更整洁。为了向读者呈现代码的原始状态,所有代码注释都不做翻译。

接触软件开发技术二十多年以来,我见过许多对于代码整洁性缺乏足够重视的开发者,不算过分地说,这体现了他们职业素养与基本功的双重缺陷。我翻译《C#编程风格》(The Elements of C# Style)和本书,实在也是希望在这方面看到开发者在重视度和实际应用方面的提升。我于2009年翻译了这本书,如今10年过去了,开发者们越来越认同“整洁代码非常重要”。在本书英文版出版后,作者对原版做了许多修正和勘误,这些修正和勘误也体现在了这一中文修订版中。这也正体现了“整洁代码派”的基本理念:不断迭代。

在本书的结束语中,“鲍勃大叔”提到别人给他的一条腕带,上面的字样是“Test Obsessed”(沉迷测试)。“鲍勃大叔”“发现自己无法取下腕带。不仅因为腕带很紧,而且那也是条精神上的紧箍咒……它一直提醒我,我做了写出整洁代码的承诺。”有了这条腕带,代码猴子成了模范童子军。我想,每位开发者都需要这样一条腕带吧。

韩 磊

2019年12月


乐嚼(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哲学包括以下概念。

如果你接受挑战——没错,就是挑战,阅读并应用本书,你就会理解和赞赏上述概念的最后一条。我们最终是在驶向一种负责任的专业精神之根源所在,这种专业性隶属于一个关注产品生命周期的专业领域。在我们遵循TPM来维护机动车和其他机械时,停机维护——等待缺陷显现出来——并不常见。我们更上一层楼:每天检查机械,在磨损机件停止工作之前就换掉它,或者按常例每1000英里(约1609.3 km)就更换润滑油以防止磨损和开裂。对于代码,应无情地做重构。还可以更进一步,就像TPM运动在数十年前的创新:一开始就打造更易维护的机械。写出可读的代码,其重要程度不亚于写出可执行的代码。1960年左右,围绕TPM引入的终极实践,关注用全新机械替代旧机械。诚如Fred Brooks所言,我们或许应该每7年就重做一次软件的主要模块,清理缓慢陈腐的代码。也许我们该把重构周期从以年计缩短到以周、以天甚至以小时计。那便是细节所在了。

细节中自有天地,而在生活中应用此类手段时也有微言大义,就像我们一成不变地对那些源自日本的做法寄予厚望一般。这并非只是东方的生活观,英美民间也遍是这类警句。上引“整顿”二字就曾出现在某位俄亥俄州牧师的笔下,他把齐整看作是“荡涤种种罪恶之良方”。“清楚”又如何呢?整洁近乎虔诚(Cleanliness is next to godliness.)。一张脏乱的桌子足以夺去一所丽宅的光彩。老话怎么说“身美”的?守小节者不亏大节(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的门把手吧,那正是整理。认真对待每个变量名。你当用为自己第一个孩子命名般的谨慎来给变量命名。

正如每位房主所知,此类照料和修葺永无休止。建筑师Christopher Alexander——模式与模式语言之父——把每个设计动作看作是较小的局部修复动作。他认为,设计良好结构才是建筑师的本职所在,而更大的建筑形态则当留给模式及居住者搬进的家私来完成。设计始终在持续进行,不只是在新建一个房间时,也在我们重新粉刷墙面、更换旧地毯或者换厨房水槽时。大多数艺术门类也持类似主张。在寻找其他推崇细节的人时,我们发现,19世纪法国作家古斯塔夫 福楼拜(Gustav Flaubert)名列其中。法国诗人保尔 瓦雷里(Paul Valery)认为,每首诗歌都无写完之时,得持续重写,直至放弃为止。全心倾注于细节,屡见于追求卓越的行为之中。虽然这无甚新意,但阅读本书对读者仍是一种挑战,你要重拾久已弃置脑后的良好规则,自发自主,“响应改变”。

不幸的是,我们往往见不到人们把对细节的关注当作编程艺术的基础要件。我们过早地放弃了在代码上的工作,并不是因为它业已完成,而是因为我们的价值体系关注外在表现甚于关注要交付之物的本质。疏忽最终结出了恶果:坏东西一再出现。无论是在行业里还是学术领域,研究者都很重视代码的整洁问题。供职于贝尔软件生产研究实验室(Bell Labs Software Production Research)——没错,就是生产!——时,我们有些不太严密的发现,认为前后一致的缩进风格明显标志了较低的缺陷率。我们原指望将质量归因于架构、编程语言或者其他高级概念;将我们的专业能力归功于对工具的掌握和各种高高在上的设计方法,没想到那些安置于厂区的机器,那些编码者,他们居然通过简单地保持一致缩进风格创造了价值,这简直是一种侮辱。我在许多年前就在书中写过,这种风格远不止是一种单纯的能力那么简单。日本式的世界观深知日常工作者的价值,而且,还深知工作者简单的日常行为所锻造的开发系统的价值。质量是上百万次全心投入的结果,而非仅归功于任何来自天堂的伟大方法。这些行为简单却不简陋,也不意味着简易。相反,它们是人力所能达的不仅伟大而且美丽的造物。忽略它们,就不能成为完整的人。

当然,我仍然提倡放宽思路,也推崇根植于深厚领域知识和软件可用性的各种架构手法的价值,但本书与此无关——至少,没有明显关系。本书精妙之处,其意义之深远,不该无人赏识。它正与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] 中文意为“素养、教养”。——译者注


(经Thom Holwerda允许复制上图)

你的代码在哪道门后面?你的团队或公司在哪道门后面?为什么会在那里?只是一次普通的代码评审,还是产品面世后才发现一连串严重问题?我们是否在战战兢兢地调试自己错以为没问题的代码?客户是否在流失?经理们是否把我们盯得如芒刺在背?当事态变得严重起来时,如何保证我们在那道正确的门后做补救工作?答案是:技艺(craftsmanship)。

习艺之要有二:知和行。你应当习得有关原则、模式和实践的知识,穷尽应知之事,并且要对其了如指掌,通过刻苦实践掌握它。

我可以教你骑自行车的物理学原理。实际上,经典数学的表达方式相对而言确实简洁明了。重力、摩擦力、角动量、质心等,用一页写满方程式的纸就能说明白。有了这些方程式,我可以为你证明出骑车完全可行,而且还可以告诉你骑车所需的全部知识。即便如此,你在初次骑车时还是会跌倒在地。

编码亦同此理。我们可以写下整洁代码的所有“感觉良好”的原则,放手让你去干(换言之,让你从自行车上摔下来)。那样的话,我们算是哪门子老师?而你又会成为怎样的学生呢?

不!本书可不会这么做。

学写整洁代码很难。它可不止于要求你掌握原则和模式,你得在这上面花工夫。你须自行实践,且体验自己的失败。你须观察他人的实践与失败。你须看看别人是怎样蹒跚学步,再转头研究他们的路数。你须看看别人是如何绞尽脑汁做出决策,又是如何为错误决策付出代价的。

阅读本书要多用心思。这可不是那种降落前就能读完的“感觉不错”的“飞机书”。本书要让你用功,而且是非常用功。如何用功?阅读代码——大量代码。而且你要去琢磨某段代码好在什么地方、坏在什么地方。在我们分解然后组合模块时,你得亦步亦趋地跟上。这会花些工夫,不过值得一试。

本书大致可分为3个部分。第一部分介绍编写整洁代码的原则、模式和实践。这部分有相当多的示例代码,读起来颇具挑战性。读完这几章,就为阅读第二部分做好了准备。如果你就此止步,只能祝你好运啦!

第二部分最需要花工夫。这部分包括几个复杂性不断增加的案例研究。每个案例都清理一些代码——把有问题的代码转化为问题少一些的代码。这部分极为详细。你的思维要在讲解和代码段之间跳来跳去。你得分析和理解那些代码,琢磨每次修改的来龙去脉。

你付出的劳动将在第三部分得到回报。这部分只有一章,列出从上述案例研究中得到的启示和灵感。在遍览和清理案例中的代码时,我们把每个操作理由记录为一种启示或灵感。我们尝试去理解自己对阅读和修改代码的反应,尽力了解为什么会有这样的感受、为什么会如此行事。结果得到了一套描述在编写、阅读、清理代码时思维方式的知识库。

如果你在阅读第二部分的案例研究时没有好好用功,那么这套知识库对你来说可能所值无几。在这些案例研究中,每次修改都仔细注明了相关启示的标号。这些标号用方括号标出,如[H22]。由此你可以看到这些启示在何种环境下被应用和编写。启示本身没有价值,启示与案例研究中清理代码的具体决策之间的关系才有价值

如果你跳过案例研究部分,只阅读了第一部分和第三部分,那就不过是又看了一本关于写出好软件的“感觉不错”的书。但如果你肯花时间琢磨那些案例,亦步亦趋——站在作者的角度,迫使自己以作者的思维路径考虑问题,就能更深刻地理解这些原则、模式、实践和启示。这样的话,就像一个人熟练地掌握了骑车的技术后,自行车就如同其身体的延伸部分那样;对你来说,本书所介绍的整洁代码的原则、模式、实践和启示就成为你本身具有的技艺,而不再是“感觉不错”的知识。

感谢两位艺术家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)一书中的大量插图。她是我的长女,常给我带来极大的愉悦。


本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,单击“提交勘误”,输入勘误信息,单击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/selfpublish/submission即可)。

如果您来自学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。

“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近30年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。

异步社区

微信服务号


本书封面的图片是M104:草帽星系(The Sombrero Galaxy)。M104坐落于处女座(Virgo),距地球仅3000万光年,其核心是一个质量超大的黑洞,有100万个太阳那么重。

这幅图是否让你想起了Klingon星球(克林贡)[1]的卫星Praxis(普拉西斯)爆炸的事?我清楚地记得,在《星舰迷航VI》中,大爆炸之后碎片四溅,飞舞出一个赤道光环的场景。至此,光环就成为科幻电影中爆炸场景的必然产物了,甚至就在《星舰迷航》系列电影的后续情节中,Alderaan(阿尔德然)的爆炸也有类似场景出现。

环绕M104的光环是什么造成的?它为何会有如此巨大的膨胀率和如此明亮而微小的内核?在我看来,仿佛那位于中心位置的黑洞勃然大怒,向星系的中心扔出了一个3万光年大的洞一般。在这场宇宙大崩塌所及范围之内的居民全都大难临头了。

超大质量的黑洞以星体为食,将星体的相当部分质量转换为能量。方程式E = MC2已经足够体现杠杆作用了,但当M有一颗星体那么大的质量时,看吧!在那巨兽酒足饭饱之前,有多少星体会一头撞进它的胃里?核心部分空洞的大小,是否说明了什么呢?

封面上的M104图片,是用来自哈勃望远镜的那幅著名的可见光相片(上图)和Spitzer(斯比泽)轨道探测器最新的红外影像(下图)组合而成。

在红外影像中,光环中的热粒子闪耀着穿过了中心膨胀体。这两幅影像组合起来,显现出我们从未见过的景象,展示了久远之前曾熊熊燃烧的火海。

封面图片:来自斯比泽太空望远镜

[1] 系列剧《星舰迷航》(Star Trek)中的故事情节,Praxis星爆炸,由此导致联邦和Klingon达成首次和平协议。


阅读本书有两种原因:第一,你是个程序员;第二,你想成为更好的程序员。很好。我们需要更好的程序员。

这是一本有关编写好程序的书。它充斥着代码。我们要从各个方向来考察这些代码。从顶向下,从底往上,从里而外。读完后,就能知道许多关于代码的事了。而且,我们还能说出好代码和糟糕的代码之间的差异。我们将了解到如何写出好代码。我们也会知道,如何将糟糕的代码改成好代码。

有人也许会以为,关于代码的书有点儿落后于时代——代码不再是问题,我们应当关注模型和需求。确实,有人说过我们正在临近代码的终结点。很快,代码就会自动产生出来,不需要再人工编写。程序员完全没用了,因为业务人员可以从规约直接生成程序。

这是不可能的!我们永远抛不掉代码,因为代码呈现了需求的细节。在某些层面上,这些细节无法被忽略或抽象,必须明确之。将需求明确到机器可以执行的细节程度,就是编程要做的事。而这种规约正是代码

我期望语言的抽象程度继续提升。我也期望领域特定语言的数量继续增加。那会是好事一桩,但那终结不了代码。实际上,在较高层次上用领域特定语言撰写的规约也将代码!它也得严谨、精确、规范和详细,好让机器理解和执行。

那帮以为代码终将消失的伙计,就像是巴望着发现一种无规范数学的数学家们一般。他们巴望着,总有一天能创造出某种机器,我们只要动动念头、嘴都不用张,就能叫它依计行事。那机器要能透彻理解我们,只有这样,它才能把含混不清的需求翻译为可完美执行的程序,精确满足需求。

这种事永远不会发生。即便是人类,倾其全部的直觉和创造力,也造不出满足客户模糊感觉的成功系统来。如果说需求规约原则教给了我们什么,那就是归置良好的需求就像代码一样正式,也能作为代码的可执行测试来使用。

记住,代码确然是我们最终用来表达需求的那种语言。我们可以创造各种与需求接近的语言。我们可以创造帮助把需求解析和汇整为正式结构的各种工具。然而,我们永远无法抛弃必要的精确性——所以代码永存。

最近我在读Kent Beck著《实现模式》(Implementation Patterns[1]一书的序。他这样写道:“……本书基于一种不太牢靠的前提:好代码的确重要……”这前提不牢靠?我反对!我认为这是该领域最强固、最受支持、最被强调的前提了(我想Kent也知道)。我们知道好代码重要,是因为其短缺实在困扰了我们太久。

20世纪80年代末,有家公司写了一个很流行的杀手应用,许多专业人士都买来用。然后,发布周期开始拉长。缺陷总是不能修复。装载时间越来越久,崩溃的概率也越来越大。至今我还记得自己在某天沮丧地关掉那个程序,从此再不用它。在那之后不久,该公司就关门大吉了。

20年后,我见到那家公司的一位早期雇员,问他当年发生了什么事。他的回答令我愈发恐惧起来。原来,当时他们赶着推出产品,代码写得乱七八糟。特性越加越多,代码也越来越烂,最后再也没法管理这些代码了。是糟糕的代码毁了这家公司

你是否曾为糟糕的代码所深深困扰?如果你是位有点儿经验的程序员,必定多次遇到过这类困境。我们有专用来形容这事的词:沼泽(wading)。我们蹚过代码的水域。我们穿过灌木密布、瀑布暗藏的沼泽地。我们拼命想找到出路,期望有点儿什么线索能揭示到底发生了什么事;但目光所及,只是越来越多死气沉沉的代码。

你当然曾为糟糕的代码所困扰过。那么——为什么要写糟糕的代码呢?

是想快点儿完成吗?是要赶时间吗?有可能。或许你觉得自己要干好而所需的时间不够;假使花时间清理代码,老板就会大发雷霆。或许你只是不耐烦再搞这套程序,期望早点儿结束。或许你看了看自己承诺要做的其他事,意识到得赶紧弄完手上的东西,好接着做下一件工作。这种事我们都干过。

我们都曾经瞟一眼自己亲手造成的混乱,决定弃之而不顾,走向新一天。我们都曾经看到自己的烂程序居然能运行,然后断言能运行的烂程序总比什么都没有强。我们都曾经说过有朝一日再回头清理。当然,在那些日子里,我们都没听过勒布朗(LeBlanc)法则:稍后等于永不(Later equals never.)。

只要你干过两三年编程,就有可能曾被某人的糟糕的代码绊倒过。如果你编程不止两三年,也有可能被这种代码拖过后腿,进度延缓的情况会很严重。有些团队在项目初期进展迅速,但有那么一两年的时间却慢如蜗行。对代码的每次修改都影响到其他两三处代码,修改无小事。每次添加或修改代码,都得对那堆扭纹柴了然于心,这样才能往上扔更多的扭纹柴。这团乱麻越来越大,再也无法理清,最后束手无策。

随着混乱的增加,团队生产力也持续下降,以致趋向于零。当生产力下降时,管理层就只有一件事可做了:增加更多人手到项目中,期望提升生产力。可是新人并不熟悉系统的设计。他们搞不清楚什么样的修改符合设计意图,什么样的修改违背设计意图。而且,他们以及团队中的其他人都背负着提升生产力的可怕压力。于是,他们只会制造更多的混乱,驱动生产力向零那端不断下降。如图1-1所示。

图1-1 生产力vs.时间

最后,开发团队造反了,他们告诉管理层,再也无法在这令人生厌的代码基础上做开发了。他们要求做全新的设计。管理层不愿意投入资源完全重起炉灶,但他们也不能否认生产力低得可怕。他们只好同意开发者的要求,授权去做一套看上去很美的华丽新设计。

于是就组建了一支新军。谁都想加入这个团队,因为它是张白纸。他们可以重新来过,搞出点真正漂亮的东西来。但只有最优秀、最聪明的家伙被选中,其余人则继续维护现有系统。

现在有两支队伍在竞赛了。新团队必须搭建一套新系统,新系统要实现旧系统的所有功能,另外,还得跟上对旧系统的持续改动。在新系统功能足以抗衡旧系统之前,管理层不会替换掉旧系统。

竞赛可能会持续极长时间。我就见过延续了十年之久的。到了完成的时候,新团队的老成员早已不知去向,而现有成员则要求重新设计一套新系统,因为这套系统太烂了。

假使你经历过哪怕是一小段我谈到的这种事,那么你一定知道,花时间保持代码整洁不但关乎效率,还关乎生存。

你可曾遇到过某种严重到要花数个星期来做本来只需数小时即可完成的事的混乱状况?你可曾见过本来只需做一行修改,结果却涉及上百个模块的情况?这种事太常见了。

怎么会发生这种事?为什么好代码会这么快就变质成糟糕的代码?理由多得很。我们抱怨需求变化背离了初期设计。我们哀叹进度太紧张,没法干好活。我们把问题归咎于那些愚蠢的经理、苛求的用户、没用的营销手段和那些电话消毒剂。不过,亲爱的呆伯特(Dilbert)[2],我们是自作自受[3]。我们太不专业了。

这话可不太中听。怎么会是自作自受呢?难道不关需求的事?难道不关进度的事?难道不关那些蠢经理和没用的营销手段的事?难道他们就不该负点责吗?

不。经理和营销人员指望从我们这里得到必需的信息,然后才能做出承诺和保证;即便他们没开口问,我们也不该羞于告知自己的想法。用户指望我们验证需求是否都在系统中实现了。项目经理指望我们遵守进度。我们与项目的规划脱不了干系,对失败负有极大的责任;特别是当失败与糟糕的代码有关时尤为如此!

“且慢!”你说。“不听经理的,我就会被炒鱿鱼。”多半不会。多数经理想要知道实情,即便他们看起来不喜欢实情。多数经理想要好代码,即便他们总是痴缠于进度。他们会奋力卫护进度和需求;那是他们该干的。你则当以同等的热情卫护代码。

再说明白些,假使你是位医生,病人请求你在给他做手术前别洗手,因为那会花太多时间,你会照办吗?[4]本该是病人说了算;但医生却绝对应该拒绝遵从。为什么?因为医生比病人更了解疾病和感染的风险。医生如果按病人说的办,就是一种不专业的态度(更别说是犯罪了)。

同理,程序员遵从不了解混乱风险的经理的意愿,也是不专业的做法。

程序员面临着一道基础价值谜题。有几年经验的开发者都知道,之前的混乱拖了自己的后腿。但开发者们背负期限的压力,只好制造混乱。简言之,他们没花时间让自己做得更快!

真正的专业人士明白,这道谜题的第二部分说错了。制造混乱无助于赶上期限。混乱只会立刻拖慢你,叫你错过期限。赶上期限的唯一方法——做得快的唯一方法 ——就是始终尽可能保持代码整洁。

假设你相信混乱的代码是祸首,假设你接受做得快的唯一方法是保持代码整洁的说法,你一定会自问:“我怎么才能写出整洁的代码?”不过,如果你不明白整洁对代码有何意义,尝试去写整洁代码就毫无所益!

坏消息是,写整洁代码很像是绘画。多数人都知道一幅画是好还是坏,但能分辨优劣并不表示懂得绘画。能分辨整洁代码和肮脏代码,也不意味着会写整洁代码!

写整洁代码,需要遵循大量的小技巧,贯彻刻苦习得的“整洁感”。这种“代码感”就是关键所在。有些人生而有之,有些人费点儿劲才能得到。它不仅让我们看到代码的优劣,还予我们以借戒规之力化劣为优的攻略。

缺乏“代码感”的程序员,看混乱只是混乱,无处着手。有“代码感”的程序员能从混乱中看出其他的可能与变化。“代码感”帮助程序员选出最好的方案,并指导程序员制订修改行动计划,按图索骥。

简言之,编写整洁代码的程序员就像是艺术家,他能用一系列变换把一块白板变作由优雅代码构成的系统。

有多少程序员,就有多少对整洁代码的定义。所以我只询问了一些非常知名且经验丰富的程序员。

Bjarne Stroustrup,C++语言发明者,《C++程序设计语言》(C++ Programming Language)一书作者。

我喜欢优雅和高效的代码。代码逻辑应当直截了当,令缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事。

Bjarne用了“优雅”一词。说得好!我MacBook上的词典提供了如下定义:外表或举止上令人愉悦的优美和雅观;令人愉悦的精致和简单。注意对“愉悦”一词的强调。Bjarne显然认为整洁的代码读起来令人愉悦。读这种代码,就像见到手工精美的音乐盒或者设计精良的汽车一般,让你会心一笑。

Bjarne也提到效率——而且两次提及。这话出自C++发明者之口,或许并不出奇;不过我认为并非是在单纯追求速度。被浪费掉的运算周期并不雅观,也不令人愉悦。留意Bjarne怎么描述那种不雅观的结果,他用了“引诱”这个词。诚哉斯言。糟糕的代码引发混乱!别人修改糟糕的代码时,往往会越改越烂。

务实的Dave Thomas和Andy Hunt从另一角度阐述了这种情况,他们提到破窗理论:窗户破损了的建筑让人觉得似乎无人照管,于是无人再去关心。他们放任窗户继续破损,最终自己也参加破坏活动,在外墙上涂鸦,任由垃圾堆积。一扇破损的窗户开辟了大厦走向倾颓的道路。

Bjarne也提到完善错误处理代码,往深处说就是在细节上花心思。敷衍了事的错误处理代码只是程序员忽视细节的一种表现。此外还有内存泄漏,还有静态条件代码,还有前后不一致的命名方式。结果就凸显出整洁代码对细节的重视。

Bjarne以“整洁的代码只做好一件事”结束论断。毋庸置疑,软件设计的许多原则最终都会归结为这句警语。有那么多人发表过类似的言论。糟糕的代码想做太多事,它意图混乱、目的含混。而整洁的代码力求集中,每个函数、每个类和每个模块都全神贯注于一事,完全不受四周细节的干扰和污染。

Grady Booch,《面向对象分析与设计》(Object Oriented Analysis and Design with Applications)一书作者

整洁的代码简单直接。整洁的代码如同优美的散文。整洁的代码从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句。

Grady的观点与Bjarne的观点有类似之处,但他从可读性的角度来定义。我特别喜欢“整洁的代码如同优美的散文”这种看法。想想你读过的某本好书。回忆一下,那些文字如何在脑中形成影像?就像是看了场电影,对吧?还不止!你还看到那些人物,听到那些声音,体验到那些喜怒哀乐。

阅读整洁的代码和阅读《指环王》(Lord of the Rings)自然不同。不过,仍有可类比之处。如一本好的小说一般,整洁的代码应当明确地展现出要解决问题的张力,它应当将这种张力推至高潮,以某种显而易见的方案解决问题,使读者发出“啊哈!本当如此!”的感叹。

窃以为Grady所谓“干净利落的抽象”(crisp abstraction),乃是绝妙的矛盾修辞法。毕竟crisp几乎就是“具体”(concrete)的同义词。我MacBook上的词典这样定义crisp一词:果断决绝,就事论事,没有犹豫或不必要的细节。尽管有两种不同的定义,但是该词还是承载了有力的信息。代码应当讲述事实,不引人猜测。它只该包含必需之物。读者应当感受到我们的果断决绝。

“老大”Dave Thomas,OTI公司创始人,Eclipse战略“教父”

整洁的代码应可由作者之外的开发者阅读和增补。它应当有单元测试和验收测试。它使用有意义的命名。它只提供一种而非多种做一件事的途径。它只有尽量少的依赖关系,而且要明确地定义和提供清晰的、尽量少的API。代码应通过其字面表达含义,因为不同的语言导致并非所有必需的信息均可通过代码自身清晰表达。

Dave老大在可读性上和Grady持相同观点,但有一个重要的不同之处。Dave断言,整洁的代码便于其他人予以增补。这看似显而易见,但亦不可过分强调。毕竟易读的代码和易修改的代码之间还是有区别的。

Dave将整洁系于测试之上!要在十年之前,这会让人大跌眼镜。但测试驱动开发(Test Driven Development)已在行业中造成了深远影响,成为基础规程之一。Dave说得对,没有测试的代码不干净。不管它有多优雅,也不管有多可读、多易理解,微乎测试,其不洁亦可知也。

Dave两次提及“尽量少”。显然,他推崇小块的代码。实际上,从有软件起人们就在反复强调这一点,越小越好。

Dave也提到,代码应在字面上表达其含义。这一观点源自Knuth的“字面编程”(literate programming)[5]。结论就是应当用人类可读的方式来写代码。

Michael Feathers,《修改代码的艺术》(Working Effectively with Legacy Code)一书作者

我可以列出我留意到的整洁代码的所有特点,但其中有一条是根本性的。整洁的代码总是看起来像是某位特别在意它的人写的。几乎没有改进的余地。代码的作者什么都想到了,如果你企图改进它,总会回到原点,赞叹某人留给你的代码——全心投入的某人留下的代码。

一言以蔽之:在意。这就是本书的题旨所在。或许该加个副标题,如何在意代码

Michael一针见血。整洁代码就是作者着力照料的代码。有人曾花时间让它保持简单有序。他们适当地关注到了细节,他们在意过。

Ron Jeffries,《极限编程实施》(Extreme Programming Installed)以及《C#极限编程探险》(Extreme Programming Adventures in C#)作者

Ron初入行就在战略空军司令部(Strategic Air Command)编写Fortran程序,此后几乎在每种机器上编写过每种语言的代码。他的言论值得咀嚼。

近年来,我开始研究贝克的简单代码规则,差不多也都琢磨透了。简单代码,依其重要顺序:

在以上诸项中,我最在意代码重复。如果同一段代码反复出现,就表示某种想法未在代码中得到良好的体现。我尽力去找出那到底是什么,然后再尽力将其更清晰地表达出来。

在我看来,有意义的命名是体现表达力的一种方式,我往往会修改好几次才会定下名字来。借助Eclipse这样的现代编码工具,重命名代价极低,所以我无所顾忌。然而,表达力还不只体现在命名上。我也会检查对象或方法是否想做的事太多。如果对象功能太多,最好切分为两个或多个对象。如果方法功能太多,我总是使用抽取手段(Extract Method)重构之,从而得到一个能较为清晰地说明其自身功能的方法,以及另外数个说明如何实现这些功能的方法。

消除重复和提高表达力让我在整洁代码方面获益良多,只要铭记这两点,改进脏代码时就会大为改观。不过,我时常关注的另一规则就不太好解释了。

这么多年下来,我发现所有程序都由极为相似的元素构成。例如“在集合中查找某物”。不管是雇员记录数据库还是键值对哈希表,或者某类条目的数组,我们都会发现自己想要从集合中找到某一特定条目。一旦出现这种情况,我通常会把实现手段封装到更抽象的方法或类中。这样做好处多多。

可以先用某种简单的手段,比如哈希表来实现这一功能,由于对搜索功能的引用指向了我那个小小的抽象,因此能随需应变,修改实现手段。这样既能快速前进,又能为未来的修改预留余地。

另外,该集合抽象常常提醒我留意“真正”在发生的事,避免随意实现集合行为,因为我真正需要的不过是某种简单的查找手段。

减少重复代码,提高表达力,提早构建简单抽象。这就是我写整洁代码的方法。

Ron以寥寥数段文字概括了本书的全部内容。不要重复代码,只做一件事——表达力,小规模抽象。该有的都有了。

Ward Cunningham,Wiki发明者,极限编程(eXtreme Programming)的创始人之一,Smalltalk语言和面向对象的思想领袖。所有在意代码者的“教父”。

如果每个例程都让你感到深合己意,那就是整洁代码。如果代码让编程语言看起来像是专为解决那个问题而存在的,就可以称之为漂亮的代码。

这种说法很Ward。它使你听了之后就点头,然后继续听下去。如此在理,如此浅显,绝不故作高深。你大概以为此言深合己意吧。再走近点看看。

“……深合己意”。你最近一次看到深合己意的模块是什么时候?模块多半都繁复难解吧?难道没有触犯规则吗?你不是也曾挣扎着想抓住一些从整个系统中散落而出的线索,编织进你在读的那个模块吗?你最近一次读到某段代码,并且如同对Ward的说法点头一般对这段代码点头,是什么时候的事了?

Ward期望你不会为整洁代码所震惊。你无须花太多力气,那么代码就是深合你意的。它明确、简单、有力。每个模块都为下一个模块做好准备。每个模块都告诉你下一个模块会是怎样的。整洁的程序好到你根本不会注意到它。设计者把它做得像一切其他设计般简单。

那Ward有关“美”的说法又如何呢?我们都曾面临语言不是为要解决的问题而设计的困境,但Ward的说法又把球踢回我们这边。他说,漂亮的代码让编程语言像是专为解决那个问题而存在!所以,让语言变得简单的责任就在我们身上了!当心,语言是冥顽不化的!是程序员让语言显得简单。

我(“鲍勃大叔”)又是怎么想的呢?在我眼中整洁代码是什么样的?本书将以详细到吓死人的程度告诉你,我和我的同道对整洁代码的看法。我们会告诉你关于整洁变量名的想法,关于整洁函数的想法,关于整洁类的想法,等等。我们视这些观点为当然,且不为其逆耳而致歉。对我们而言,在职业生涯的这个阶段,这些观点确属当然,也是我们整洁代码派的圭旨。

武术家从不认同所谓最好的武术,也不认同所谓绝招。武术大师们常常创建自己的流派,聚徒而授。因此我们才看到格雷西家族在巴西开创并传授的格雷西柔术(Gracie Jiu Jistu),看到奥山龙峰(Okuyama Ryuho)在东京开创并传授的八光流柔术(Hakkoryu Jiu Jistu),看到李小龙(Bruce Lee)在美国开创并传授的截拳道(Jeet Kune Do)。

弟子们沉浸于创始人的授业。他们全心师从某位师傅,排斥其他师傅。弟子有所成就后,可以转投另一位师傅,扩展自己的知识与技能。有些弟子最终百炼成钢,创出新招数,开宗立派。

任何门派都并非绝对正确。不过,身处某一门派时,我们总以其所传之技为善。归根结底,练习八光流柔术或截拳道,自有其善法,但这并不能否定其他门派所授之法。

可以把本书看作是对象导师(Object Mentor)[6]整洁代码派的说明。书中要传授的就是我们勤操己艺的方法。如果你遵从这些教诲,你就会如我们一般乐受其益,你将学会如何编写整洁而专业的代码。但无论如何也别错以为我们是“正确的”。其他门派和师傅同我们一样专业。你有必要也向他们学习。

实际上,书中很多建议都存在争议。或许你并不完全同意这些建议,甚至可能会强烈反对其中的一些建议。这样挺好的。我们不能要求做最终权威。另外,书中列出的建议,乃是我们长久苦思,从数十年的从业经验和无数尝试与错误中得来的。无论你同意与否,如果你没看到或是不尊重我们的观点,就真该自己害臊。

Javadoc中的@author字段告诉我们自己是什么人。我们是作者。作者都有读者。实际上,作者有责任与读者做良好沟通。下次你写代码的时候,记得自己是作者,要为评判你工作的读者写代码。

你或许会问:代码真正“读”的成分有多少呢?难道主要力量不是应该用在“写”上吗?

你是否玩过“编辑器回放”?20世纪80、90年代,Emac之类的编辑器可以记录每次的击键动作。你可以在工作一小时之后,回放击键过程,就像是看一部高速电影。我这么做过,结果很有趣。

回放过程显示,多数时间都是在滚动屏幕,浏览其他模块!

鲍勃进入模块。

他向下滚动到要修改的函数。

他停下来考虑可以做什么。

哦,他滚动到模块顶端,检查变量初始化。

现在他回到修改处,开始键入。

喔,他删掉了键入的内容。

他重新键入。

他又删除了!

他键入了一半的东西,又被删除掉。

他滚动到调用要修改函数的另一函数,看看是怎么调用的。

他回到修改处,重新键入刚才删掉的代码。

他停下来。

他再一次删掉代码!

他打开另一个窗口,查看别的子类。那是个复载函数吗?

……

你该明白了。花费读与写时间的比例超过10:1。写新代码时,我们一直在读旧代码。

既然比例如此之高,我们就想让读的过程变得轻松,即便那会使编写过程更难。不可能光写不读,所以使之易读实际也就是使之易写。

这事概无例外。不读周边代码的话就没法写代码。编写代码的难度,取决于读周边代码的难度。要想干得快,要想早点做完,要想轻松写代码,先让代码易读吧。

光把代码写好可不够。必须时时保持代码整洁。我们都见过代码随时间流逝而腐坏。我们应当更积极地阻止腐坏的发生。

借用美国童子军一条简单的军规,应用到我们的专业领域:

让营地比你来时更干净。[7]

如果每次签入时,代码都比签出时干净,那么代码就不会腐坏。清理并不一定要花多少功夫,也许只是改好一个变量名,拆分一个有点过长的函数,消除一点点重复代码,清理一个嵌套if语句。

你想要为一个代码随时间流逝而越变越好的项目工作吗?你还能相信有其他更专业的做法吗?难道持续改进不是专业性的内在组成部分吗?

从许多角度看,本书都是我2002年写的那本《敏捷软件开发:原则、模式与实践》(Agile Software DevelopmentPrinciplesPatternsand Practices,简称PPP)的“前传”。PPP关注面向对象设计的原则,以及专业开发者采用的许多实践方法。假如你没读过PPP,你会发现它像本书的延续。如果你读过,你会发现那本书的主张在代码层面于本书中回响。

在本书中,你会发现对不同设计原则的引用,包括单一权责原则(Single Responsibility Principle,SRP)、开放闭合原则(Open Closed Principle,OCP)和依赖倒置原则(Dependency Inversion Principle,DIP)等。

艺术书并不保证你读过之后能成为艺术家,只能告诉你其他艺术家用过的工具、技术和思维过程。本书同样也不担保让你成为好程序员。它不担保能给你“代码感”。它所能做的,只是展示好程序员的思维过程,还有他们使用的技巧、技术和工具。

和艺术书一样,本书也充满了细节。代码会很多,你会看到好代码,也会看到糟糕的代码。你会看到糟糕的代码如何转化为好代码。你会看到启发、规条和技巧的列表。你会看到一个又一个例子。但最终结果取决于你自己。

还记得那个关于小提琴家在去表演的路上迷路的老笑话吗?他在街角拦住一位长者,问他怎么才能去卡耐基音乐厅(Carnegie Hall)。长者看了看小提琴家,又看了看他手中的琴,说道:“你还得练,孩子,还得练!”

[Beck07]:Implementation Patterns, Kent Beck, Addison-Wesley, 2007.

[Knuth92]:Literate Programming, Donald E. Knuth, Center for the Study of Language and Information, Leland Stanford Junior University, 1992.

[1] [Beck07]。

[2] 著名IT讽刺漫画。——译者注

[3] 原文为But the fault, dear Dilbert, is not in our stars, but in ourselves.脱胎自莎士比亚戏剧《裘力斯·凯撒》第一幕第二场凯些斯的台词The fault, dear Brutus, is not in our stars, but in ourselves, that we are underlings.(若我们受人所制,亲爱的勃鲁托斯,那错也在我们身上,不能怪罪命运。)——译者注

[4] 1847年Ignaz Semmelweis(伊纳兹·塞麦尔维斯)提出医生应洗手的建议时,遭到了反对,人们认为医生太忙,接诊时无暇洗手。

[5] [Knuth92]。

[6] 本书主要作者Robert C.Martin开办的技术咨询和培训公司。——译者注

[7] 摘自Robert Stephenson Smyth Baden-Powell(英国人,童子军创始者)对童子军的遗言:“努力,让世界比你来时干净些……”


Tim Ottinger

软件中随处可见命名。我们给变量、函数、参数、类和封包命名。我们给源代码及源代码所在目录命名。我们给jar文件、war文件和ear文件命名。我们命名、命名,不断命名。既然有这么多命名要做,不妨做好它。本章列出了起个好名字应遵从的几条简单规则。

名副其实说起来简单。我们想要强调,这事很严肃。选个好名字要花时间,但省下来的时间比花掉的多。注意命名,而且一旦发现有更好的名称,就换掉旧的。这么做,读你代码的人(包括你自己)都会更开心。

变量、函数或类的名称应该已经答复了所有的大问题。它该告诉你,它为什么会存在,它做什么事,应该怎么用。如果名称需要注释来补充,那就不算是名副其实。

int d;  // elapsed time in days

名称d什么也没说明。它没有引起读者对时间消逝的感觉,更别说以日计了。我们应该选择指明了计量对象和计量单位的名称:

int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;

选择体现本意的名称能让人更容易理解和修改代码。下列代码的目的何在?

public List<int[]> getThem() {
  List<int[]> list1 = new ArrayList<int[]>();
  for (int[] x : theList) 
    if (x[0] == 4) 
      list1.add(x);
  return list1;
}

为什么难以说明上述代码要做什么事?里面并没有复杂的表达式,空格和缩进中规中矩,只用到三个变量和两个常量,甚至没有涉及任何其他类或多态方法,只是(或者看起来是)一个数组的列表而已。

问题不在于代码的简洁度,而在于代码的模糊度:即上下文在代码中未被明确体现的程度。上述代码要求我们了解类似以下问题的答案:

(1)theList中是什么类型的东西?

(2)theList零下标条目的意义是什么?

(3)值4的意义是什么?

(4)我怎么使用返回的列表?

问题的答案没体现在代码段中,可代码段就是它们该在的地方。比方说,我们在开发一种扫雷游戏,我们发现,盘面是名为theList的单元格列表,那就将其名称改为gameBoard

盘面上每个单元格都用一个简单数组表示。我们还发现,零下标条目是一种状态值,而该种状态值为4表示“已标记”。只要改为有意义的名称,代码就会得到相当程度的改进:

public List<int[]> getFlaggedCells()  {
  List<int[]> flaggedCells = new ArrayList<int[]>();
  for (int[] cell : gameBoard)
    if (cell[STATUS_VALUE] == FLAGGED)
      flaggedCells.add(cell);
  return flaggedCells;
}

注意,代码的简洁性并未被触及。运算符和常量的数量全然保持不变,嵌套数量也全然保持不变,但代码变得明确多了。

还可以更进一步,不用int数组表示单元格,而是另写一个类。该类包括一个名副其实的函数(称为isFlagged),从而掩盖住那个魔术数[1]。于是得到函数的新版本:

public List<Cell> getFlaggedCells()  {
  List<Cell> flaggedCells = new ArrayList<Cell>();
  for (Cell cell : gameBoard) 
    if (cell.isFlagged()) 
      flaggedCells.add(cell); 
  return flaggedCells;
}

只要简单改一下名称,就能轻易知道发生了什么。这就是选用好名称的力量。

程序员必须避免留下掩藏代码本意的错误线索。应当避免使用与本意相悖的词,例如,hpaixsco都不该用作变量名,因为它们都是Unix平台或类Unix平台的专有名称。即便你是在编写三角计算程序,hp看起来是一个不错的缩写[2],但那也可能会提供错误信息。

别用accountList来指称一组账号,除非它真的是List类型。List一词对程序员有特殊意义。如果包纳账号的容器并非真是一个List,就会引起错误的判断[3]。所以,用accountGroupbunchOfAccounts,甚至直接用accounts都会好一些。

提防使用外形相似度较高的名称。例如,想区分模块中某处的XYZControllerFor-EfficientHandlingOfStrings和另一处的XYZControllerForEfficientStorage-OfStrings,会花多长时间呢?这两个词的外形实在太相似了。

以同样的方式拼写出同样的概念才是信息。拼写前后不一致就是误导。我们很享受现代Java编程环境的自动代码完成特性。键入某个名称的前几个字母,按一下某个热键组合(如果有的话),就能得到一列该名称的可能形式。假如相似的名称依字母顺序放在一起,且差异很明显,那就会相当有助益,因为程序员多半会压根不看你的详细注释,甚至不看该类的方法列表就直接看名字挑一个对象。

误导性名称真正可怕的例子,是用小写字母l和大写字母O作为变量名,尤其是在组合使用的时候。当然,问题在于它们看起来完全像是常量“壹”和“零”。

int a = l;
if (O == l)
  a = O1;
else
  l = 01;

读者可能会认为这纯属虚构,但我们确曾见过充斥这类名称的代码。有一次,代码作者建议用不同字体写变量名,好显得更清楚些,但前提是这种方案得要通过口头和书面传递给未来所有的开发者才行。后来,只是做了简单的重命名操作,就解决了问题,而且也没引起别的问题。

如果程序员只是为满足编译器或解释器的需要而写代码,就会制造麻烦。例如,因为同一作用范围内两样不同的东西不能重名,你可能会随手改掉其中一个的名称,有时干脆以错误的拼写充数,结果就会出现在更正拼写错误后导致编译器出错的情况。[4]

光是添加数字系列或是废话远远不够,即便这足以让编译器满意。如果名称必须相异,那么其意思也应该不同才对。

以数字系列命名(a1a2aN)是依义命名的对立面。这样的名称纯属误导——完全没有提供正确信息,没有提供导向作者意图的线索。试看:

public static void copyChars(char a1[], char a2[]) {
  for (int i = 0; i < a1.length; i++) {
    a2[i] = a1[i];
  }
}

如果参数名改为sourcedestination,这个函数就会像样许多。

废话是另一种没意义的区分。假设你有一个Product类,如果还有一个名为ProductInfoProductData的类,那它们的名称虽然不同,意思却无区别。InfoData就像aanthe一样,是意义含混的废话。

注意,只要体现出有意义的区分,使用athe这样的前缀就没错。例如,你可能把a用在域内变量,而把the用于函数参数[5]。但如果你已经有一个名为zork的变量,又想调用一个名为theZork的变量,麻烦就来了。

废话都是冗余。variable一词永远不应当出现在变量名中。table一词永远不应当出现在表名中。NameString会比Name好吗?难道Name会是一个浮点数?如果是这样,就违反了关于误导的规则。设想有一个名为Customer的类,还有一个名为CustomerObject的类,它们的区别何在呢?哪一个是表示客户历史支付情况的最佳方式?

有一个应用反映了这种状况。为当事者讳,我们改了一下,不过犯错的代码的确就是这个样子:

getActiveAccount(); 
getActiveAccounts(); 
getActiveAccountInfo();

程序员怎么知道该调用哪个函数呢?

如果缺少明确约定,那么变量moneyAmountmoney就没区别,customerInfocustomer没区别,accountDataaccount没区别,theMessage也与message没区别。要区分名称,就要以读者能鉴别不同之处的方式来区分。

人类长于记忆和使用单词。大脑的相当一部分就是用来容纳和处理单词的。单词能读得出来。人类的大脑中有那么大的一块地方用来处理言语,若不善加利用,实在是种耻辱。

如果名称读不出来,讨论的时候就会像个傻鸟。“哎,这儿,鼻涕阿三喜摁踢(bee cee arr three cee enn tee)[6]上头,有个皮挨死极翘(pee ess zee kyew)[7]整数,看见没?”这不是小事,因为编程本就是一种社会活动。

有一家公司,程序里面写了一个genymdhms(生成日期,年、月、日、时、分、秒),他们一般读作“gen why emm dee aich emm ess”[8]。我有见字照拼读的恶习,于是开口就念“gen-yah-mudda-hims”。后来好些设计师和分析师都有样学样,听起来傻乎乎的。我们知道典故,所以会觉得很搞笑。搞笑归搞笑,实际是在强忍糟糕的命名。在给新开发者解释变量名的意义时,他们总是读出傻乎乎的自造词,而非恰当的英语词。比较

class DtaRcrd102 {
  private Date genymdhms;
  private Date modymdhms;
  private final String pszqint = "102";
  /*  ...  */
};

class Customer {
  private Date generationTimestamp; 
  private Date modificationTimestamp;
  private final String recordId = "102";
  /*  ...  */
};

现在读起来就像人话了:“喂,Mikey,看看这条记录!生成时间戳(generation timestamp)[9]被设置为明天了!不能这样吧?”

对于单字母名称和数字常量,有一个问题,就是很难在一大篇文字中找出来。

MAX_CLASSES_PER_STUDENT很容易,但想找数字7就麻烦了,它可能是某些文件名或其他常量定义的一部分,出现在因不同意图而采用的各种表达式中。如果该常量是个长数字,又被人错改过,就会逃过搜索,从而造成错误。

同样,e也不是一个便于搜索的好变量名,它是英文中最常用的字母,在每个程序、每段代码中都有可能出现。由此而见,长名称胜于短名称,搜得到的名称胜于用自造编码代写就的名称。

窃以为单字母名称用于短方法中的本地变量。名称长短应与其作用域大小相对应 [N5]。若变量或常量可能在代码中多处使用,则应赋予其便于搜索的名称。再比较

for (int j=0; j<34; j++) {
  s += (t[j]*4)/5;
}

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j=0; j < NUMBER_OF_TASKS; j++) {
  int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
  int realTaskWeeks = (realTaskdays / WORK_DAYS_PER_WEEK);
  sum += realTaskWeeks;
}

注意,上面代码中的sum并非特别有用的名称,不过至少搜得到它。采用能表达意图的名称,貌似拉长了函数代码,但要想想看,WORK_DAYS_PER_WEEK比数字5好找得多,而列表中也只剩下了体现作者意图的名称。

编码已经太多,无谓再自找麻烦。把类型或作用域编进名称里面,徒然增加了解码的负担。没理由要求每位新人都在弄清要应付的代码之外(那算是正常的),还要再搞懂另一种编码“语言”。这对解决问题而言,纯属多余的负担。带编码的名称通常也不便发音,容易打错。

在往昔名称长短很重要的时代,我们毫无必要地破坏了不编码的规矩,如今后悔不迭。Fortran语言要求首字母体现出类型,导致了编码的产生。BASIC语言的早期版本只允许使用一个字母再加上一位数字。匈牙利语标记法[10](Hungarian Notation,HN)将这种态势愈演愈烈。

在Windows的C语言API的时代,HN相当重要,那时所有名称要么是一个整数句柄,要么是一个长指针或者void指针,要不然就是string的几种实现(有不同的用途和属性)之一。那时候编译器并不做类型检查,程序员需要匈牙利语标记法来帮助自己记住类型。

现代编程语言具有更丰富的类型系统,编译器也记得并强制使用类型。而且,程序员趋向于使用更小的类、更短的方法,好让每个变量的定义都在视野范围之内。

Java程序员不需要类型编码,因为对象是强类型的,代码编辑环境已经先进到在编译开始前就能监测到类型错误的程度!所以,如今HN和其他的类型编码形式都纯属多余。它们增加了修改变量、函数或类的名称或类型的难度,它们增加了阅读代码的难度,它们制造了让编码系统误导读者的可能性。

PhoneNumber  phoneString;
//  name not changed when type changed!

也不必用m_前缀来标明成员变量。应当把类和函数做得足够小,以消除对成员前缀的需要。你应当使用某种可以高亮或用颜色标出成员的编辑环境。

public class Part {
  private String m_dsc; // The textual description
  void setName(String name) {
    m_dsc = name;
  }
}
--------------------------------------------------------------------------------------
public class Part { 
  String description;
  void setDescription(String description) {
    this.description = description;
  }
}

此外,人们会很快学会无视前缀(或后缀),而只看到名称中有意义的部分。代码读得越多,眼中就越没有前缀。最终,前缀变作了不入法眼的废料,变作了旧代码的标志物。

有时也会出现采用编码的特殊情形。比如,你在做一个创建形状用的抽象工厂(Abstract Factory),该工厂是一个接口,要用具体类来实现。你怎么来命名工厂和具体类呢?IShapeFactoryShapeFactory吗?我喜欢不加修饰的接口。前导字母I被滥用到了说好听点儿是干扰,说难听点儿根本就是废话的程度。我不想让用户知道我给他们的是接口,而就想让他们知道那是一个ShapeFactory。如果在接口和实现中必须选其一来编码的话,我宁肯选择实现。ShapeFactoryImp,甚至是丑陋的CShapeFactory,都比对接口名称编码好。

不应当让读者在脑中把你的名称翻译为他们熟知的名称,这种问题经常出现在选择是使用问题领域术语还是解决方案领域术语时。

单字母变量名就是个问题。在作用域较小、没有名称冲突时,循环计数器自然有可能被命名为ijk。(但千万别用字母l!)这是因为传统上惯用单字母名称做循环计数器。然而,在多数其他情况下,单字母名称不是个好选择;读者必须在脑中将它映射为真实概念。仅仅是因为有了ab,就要起名为c,这实在不是像样的理由。

程序员通常都是聪明人。聪明人有时会借脑筋急转弯炫耀其聪明。总而言之,假使你记得r代表不包含主机名和模式(scheme)的小写字母版url的话,那你真是太聪明了。

聪明程序员和专业程序员之间的区别在于,专业程序员了解,明确是王道。专业程序员善用其能,编写其他人能理解的代码。

类名和对象名应该是名词或名词短语,例如Customer、WikiPageAccountAddressParser。应避免使用ManagerProcessorDataInfo这样的类名。类名不应当是动词。

方法名应当是动词或动词短语,如postPaymentdeletePagesave。属性访问器(accessor)、修改器(mutator)和断言(predicate)应该根据其值命名,并依Javabean标准加上前缀getsetis

string name = employee.getName();
customer.setName("mike");
if (paycheck.isPosted())...

重载构造器时,使用描述了参数的静态工厂方法名。例如,

Complex fulcrumPoint = Complex.FromRealNumber(23.0);

通常好于

Complex fulcrumPoint = new  Complex(23.0);

可以考虑将相应的构造器设置为private,强制使用这种命名手段。

如果名称太耍宝,那就只有同作者一般有幽默感的人才能记得住,而且还是在他们记得那个笑话的时候才行。谁会知道名为HolyHand-Grenade[11]的函数是用来做什么的呢?没错,这名字挺有趣,不过DeleteItems[12]或许是更好的名称。宁可明确,毋为好玩。

抖机灵在代码中经常体现为使用俗话或俚语。例如,别用whack()[13]来表示kill()。别用eatMyShorts()[14]这类与文化紧密相关的笑话来表示abort()

言到意到。意到言到。

给每个抽象概念选一个词,并且一以贯之。例如,使用fetchretrieveget来给在多个类中的同种方法命名。你怎么记得住是哪个类中的哪个方法呢?很悲哀,你总得记住编写库或类的公司、机构或个人,才能想得起来用的是哪个术语。否则,就得耗费大把时间浏览各个文件头及前面的代码。

Eclipse和IntelliJ之类现代编程环境提供了与环境相关的线索,比如某个对象能调用的方法列表。不过要注意,列表中通常不会给出你为函数名和参数列表编写的注释。如果参数名称来自函数声明,你就太幸运了。函数名称应当独一无二,而且要保持一致,这样你才能不借助多余的浏览就找到正确的方法。

同样,如果在同一堆代码中有controller,有manager,还有driver,就会令人困惑。DeviceManagerProtocol Controller之间有何根本区别?为什么不全用controllermanager呢?它们都是Drivers吗?这种名称,让人觉得这两个对象是不同类型的,也分属不同的类。

对于那些会用到你代码的程序员,一以贯之的命名法简直就是天降福音。

避免将同一单词用于不同目的。同一术语用于不同概念,基本上就是双关语了。如果遵循“一词一义”规则,可能在好多个类里面都会有add方法。只要这些add方法的参数列表和返回值在语义上等价,就一切顺利。

但是,可能会有人决定为“保持一致”而使用add这个词来命名,即便并非真的想表示这种意思。比如,在多个类中都有add方法,该方法通过增加或连接两个现存值来获得新值。假设要写一个新类,该类中有一个方法,把单个参数放到群集(collection)中。该把这个方法叫作add吗?这样做貌似和其他add方法保持了一致,但实际上语义却不同,应该用insertappend之类的词来命名才对。把该方法命名为add,就是双关语了。

代码作者应尽力写出易于理解的代码。我们想把代码写得让别人能一目了然,而不必殚精竭虑地研究。我们想要那种大众化的作者尽责写清楚的平装书模式,而不想要那种学者挖地三尺才能明白个中意义的学院派模式。

记住,只有程序员才会读你的代码。所以,尽管用那些计算机科学(Computer Science,CS)术语、算法名、模式名、数学术语吧。依据问题所涉领域来命名可不算是聪明的做法,因为不该让协作者老是跑去问客户每个名称的含义,其实他们早该通过另一名称了解这个概念了。

对于熟悉访问者(VISITOR)模式的程序员来说,名称AccountVisitor富有意义。哪个程序员会不知道JobQueue的意思呢?程序员要做太多技术性工作,给这些事起个技术性的名称,通常是最靠谱的做法。

如果不能用程序员熟悉的术语来给手头的工作命名,就采用从所涉问题领域而来的名称吧。至少,负责维护代码的程序员就能去请教领域专家了。

优秀的程序员和设计师,其工作之一就是分离解决方案领域和问题领域的概念。与所涉问题领域更为贴近的代码,应当采用源自问题领域的名称。

很少有名称是能自我说明的——多数都不能。反之,你需要用命名良好的类、函数或名称空间来放置名称,给读者提供语境。如果没这么做,给名称添加前缀就是最后一招了。

设想你有名为firstNamelastNamestreethouseNumbercitystatezipcode的变量。把它们放一块儿的时候,很明显构成了一个地址。但是,假使只是在某个方法中看见孤零零的一个state变量呢?你会理所当然地推断那是某个地址的一部分吗?

可以添加前缀addrFirstNameaddrLastNameaddrState等,以此提供语境。至少,读者会明白这些变量是某个更大结构的一部分。当然,更好的方案是创建名为Address的类。这样,即便是编译器也会知道这些变量隶属某个更大的概念了。

看看代码清单2-1中的方法,以下变量是否需要更有意义的语境呢?函数名仅给出了部分语境;算法提供了剩下的部分。遍览函数后,你会知道numberverbpluralModifier这3个变量是“测估”信息的一部分。不幸的是这样的语境得靠读者推断出来。第一眼看到这个方法时,这些变量的含义完全不清楚。

代码清单2-1 语境不明确的变量

private void printGuessStatistics(char candidate, int count) { 
  String number;
  String verb;
  String pluralModifier; 
  if (count == 0) { 
    number = "no";
    verb = "are";
    pluralModifier = "s";
  } else if (count == 1) {
    number = "1";
    verb = "is";
    pluralModifier = "";
  } else {
    number = Integer.toString(count);
    verb = "are";
    pluralModifier = "s";
  }
  String guessMessage = String.format(
    "There %s %s %s%s", verb, number, candidate, pluralModifier
  );
  print(guessMessage);
}

上述函数有点儿过长,变量的使用贯穿始终。要分解这个函数,需要创建一个名为GuessStatisticsMessage的类,把3个变量做成该类的成员字段,这样它们就在定义上变作了GuessStatisticsMessage的一部分。语境的增强也让算法能够通过分解为更小的函数而变得更为干净利落(如代码清单2-2所示)。

代码清单2-2 有语境的变量

public class GuessStatisticsMessage {
  private String number;
  private String verb;
  private String pluralModifier;

  public String make(char candidate, int count) { 
    createPluralDependentMessageParts(count); 
    return String.format(
      "There %s %s %s%s",
       verb, number, candidate, pluralModifier );
  }

  private void createPluralDependentMessageParts(int count) {
    if (count == 0) {
      thereAreNoLetters();
    } else if (count == 1) {
      thereIsOneLetter();
    } else {
      thereAreManyLetters(count);
    }
  }

  private void thereAreManyLetters(int count) {
    number = Integer.toString(count);
    verb = "are";
    pluralModifier = "s";
  }

  private void thereIsOneLetter() {
    number = "1";
    verb = "is";
    pluralModifier = "";
  }

  private void thereAreNoLetters() {
    number = "no";
    verb = "are";
    pluralModifier = "s";
  }
}

假若有一个名为“加油站豪华版”(Gas Station Deluxe)的应用,在其中给每个类添加GSD前缀就不是什么好点子。说白了,你是在和自己在用的工具过不去。输入G,按下自动完成键,结果会得到系统中全部类的列表,列表恨不得有一英里那么长。这样做聪明吗?为什么要搞得IDE没法帮助你?

再比如,你在GSD应用程序中的记账模块创建了一个表示邮件地址的类,然后给该类命名为GSDAccountAddress。稍后,你的客户联络应用中需要用到邮件地址,你会用GSDAccountAddress吗?这名字听起来没问题吗?在这17个字母里面,有10个字母纯属多余,和当前语境毫无关联。

只要短名称足够清楚,就比长名称好。别给名称添加不必要的语境。

对Address类的实体来说,accountAddresscustomerAddress都是不错的名称,不过用在类名上就不太好了。Address是一个好类名。如果需要与邮政编码MAC地址和Web地址相区分,我会考虑使用PostalAddressMACURI。这样的名称更为精确,而精确正是命名的要点。

起好名字最难的地方在于需要良好的描述技巧和共有文化背景。与其说这是一种技术、商业或管理问题,还不如说是一种教学问题。其结果是,这个领域内的许多人都没能学会做得很好。

我们有时会担心其他开发者反对重命名。讨论一下就会知道,如果名称改得更好,那大家真的会感激你。多数时候我们并不记忆类名和方法名,而使用现代工具应对这些细节,好让自己集中精力把代码写得像词句篇章,至少像是表和数据结构(词句并非总是呈现数据的最佳手段)。改名可能会让某些人吃惊,就像你做到其他代码改善工作一样。别让这种事阻碍你前进的步伐。

不妨试试上面这些规则,看看你的代码可读性是否有所提升。如果你是在维护别人写的代码,使用重构工具来解决问题,效果会立竿见影,而且会持续下去。

[1] 即表示已标记的`4`。——译者注

[2] 即`hypotenuse`的缩写。——译者注

[3] 如后文提到的,即便容器就是一个`List`,最好也别在名称中写出容器类型名。

[4] 例如,就因为`class`已有他用,就给一个变量命名为`klass`,这真是可怕的做法。

[5]“ 鲍勃大叔”惯于在C++中这样做,但后来放弃了,因为现代IDE使这种做法变得没必要了。

[6] BCR3CNT的逐个字母读音。——译者注

[7] PSZQ的逐个字母读音。——译者注

[8] YMDHMS的逐个字母读音。——译者注

[9] 读到generation timestamp时,立刻就能与代码中的generationTimestamp变量对应上。——译者注

[10] Charles Simonyi在微软任首席架构师时推广了这种命名法。Simonyi是匈牙利人,在匈牙利语中,姓置于名前。匈牙利语命名法明确写出类型,并将类型置于实际名称前,以大写字母间隔,就像匈牙利语姓名的排列一般。例如,bBusy代表一个类型为boolean、名称为Busy的变量。——译者注

[11] 即“圣手雷”。在英国喜剧团体Monty Python的电影《巨蟒与圣杯》(Monty Python and the Holy Grail)中亚瑟王使用“圣手雷”(影射象征英国王权的权珠)炸死了一只凶恶的兔子。——译者注

[12] 意为“删除条目”。——译者注

[13] 美国俚语,劈砍。——译者注

[14] 美国俚语,去死吧。——译者注


相关图书

YOLO目标检测
YOLO目标检测
深入Activiti流程引擎:核心原理与高阶实战
深入Activiti流程引擎:核心原理与高阶实战
Serverless核心技术和大规模实践
Serverless核心技术和大规模实践
深入浅出Windows API程序设计:编程基础篇
深入浅出Windows API程序设计:编程基础篇
Spring Cloud微服务架构实战
Spring Cloud微服务架构实战
读源码学架构:系统架构师思维训练之道
读源码学架构:系统架构师思维训练之道

相关文章

相关课程