阅读本书有两种原因:第一,你是一个程序员;第二,你想成为更好的程序员。
第一章 整洁代码
代码会不会消失?人工智能会不会替代程序员的工作?
作者认为我们永远抛弃不掉代码,因为代码呈现了需求的细节,无法被忽略或者抽象,必须明确之。即便是人类,倾其直觉和创造力,也造不出满足客户模糊感觉的成功系统来。语言的抽象成都会继续提升,领域特定语言的数量也会继续增加,但代码终结不了。从我的认知来看,或许有些程序员的部分工作会被取代,最近微软发布了一个通过草图生成界面的软件,一方面我对其代码质量表示怀疑,是否方便维护,另一方面,这种生成的界面其设计水平是否可以达到上线的需求也有待商榷,或许只是对于原型来说是一个很好的辅助工具。
糟糕的代码
我们有专有的词来形容糟糕的代码:沼泽(wading)。这些代码怎么产生的呢?或许是赶时间快点完成任务,总是想着回头再清理,而勒布朗法则告诉我们:稍后等于永不(Later equals never)。制造混乱无助于赶上期限,它只会拖慢你,让你错过期限。所以你始终尽可能要保持代码整洁。写整洁代码,需要遵循大量的小技巧,贯彻刻苦习得的“整洁感”。
什么是整洁代码
Bjarne Stroustrup,C++之父
我喜欢优雅和高效的代码。代码逻辑应当直截了当,叫缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事。
Grady Booch,《Object Oriented Analysis and Design with Applications》作者
整洁的代码简单直接。整洁的代码如同优美的散文,从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句。
Dave Thomas,OTI公司创始人,Eclipse战略教父
整洁的代码应可由作者之外的开发者阅读和增补。它应有单元测试和验收测试,使用有意义的命名,它只提供一种而非多种做一件事的途径。它只有尽量少的依赖关系,而且要明确定义和提供尽量少的API。代码应通过其字面表达含义。
Michael Feathers,《Working Effectively with Legacy Code》作者
整洁代码根本性的特点:总是看起来像是某位特别在意它的人写的。几乎没有改进的余地,代码作者什么都想到了,如果你企图改进它,总会回到原点,赞叹某人留给你的代码。
第二章 有意义的命名
名副其实
变量、函数或类的名称应该已经答复了所有的大问题,它告诉它你为什么会存在,它做什么事,应该怎么用。注意命名,而且一旦发现有更好的名称,就替换掉旧的。如果名称需要注释来补充,那就不算是名副其实。
避免误导
- 程序员必须避免留下掩藏代码本意的错误线索。例如:hp、aix、sco都不应该用变量名,它们都是专有名称;
- 别用accountList来指称一组账号,除非它真的是一个List类型,用accountGroup或bunchOfAccounts,甚至accounts都会好一些。即便容器是一个List,最好也别在名称中写出容器类型名。
- 提防使用差异较小的名称。两个名字外形相似不利于查找和区分。
- 以同样的方式拼写出同样的概念才是信息。个人推荐每个Team对于一些专有名字的命名有一个checklist。
做有意义的区分
-
Info和Data就像a,an和the一样,是意义含混的废话。
-
废话就是冗余。Variable一词永远不应当出现在变量名中,Table不应当出现在表名中
程序员怎么能知道该调用哪个函数呢?
getActiveAccount();
getActiveAccounts();
getActiveAccountInfo();
使用读得出来的名称:人类长于记忆和使用单词,应该尽量避免使用自造词。
使用可搜索的名称:名称长短应与其作用域大小相对应。对于全局变量或常量,应该赋予便于搜索的名称。
避免使用编码
-
Java程序员不需要在名字中体现类型,对象都是强类型的。
-
不需要用m_前缀来标明成员变量
-
有时候会出现采用编码的特殊情形,比如在接口前加I,但作者认为被滥用了,使用者并不需要知道是接口,如果接口和实现必须选一个进行编码的话,ShapeFactoryImpl更好。
类名和方法名
类名和对象名都应该是名词或名词短语,如Customer、Wi 看 iP 阿哥、Account和AddressParser,避免使用Manager、Processor、Data或Info这样的类名。
方法名应当是动词或动词短语,如postPayment、deletePage或save。
其他
每个概念对应一个词,并且一以贯之。例如,不要将fetch, retrieve和get混用。
别用双关语,避免将同一个单词用于不同目的。
第三章 函数
短小,只做一件事,做好这件事。
每个函数一个抽象层级
Switch语句,将其埋到抽象工厂地下,不让任何人看到。
参数:最理想的参数数量是0,其次是一,再次是二,应尽量避免三。标识参数丑陋不堪,isTrue;
无副作用,避免对类变量做出未预期的改动;避免把变量搞成函数参数或系统全局变量
错误处理
-
使用异常替代返回错误码
-
抽离Try/Catch代码块,把主体部分抽离出来,另外形成函数;
-
错误处理就是一件事,try/catch/finally前后不应该有其他内容
别重复自己
不断打磨代码:分解函数,修改名词,消除重复。
第四章 注释
别给糟糕的代码加注释——重新写吧
注释是弥补我们在用代码表达意图时遭遇的失败,注释总是一种失败。代码在变动,在演化,但程序员不能坚持维护注释。只有代码能忠实地告诉你它做的事。
好注释
- 法律信息
- 提供基本信息的注释
- 对意图的解释
- 阐释
- 警示
- TODO注释
- 公共API中的Javadoc
坏注释
- 喃喃自语
- 多余的注释,简单函数的头部注释都是多余的。
- 每个函数都要有Javadoc或每个变量都有注释事愚蠢的,会让代码变得散乱
- 日志式注释,模块开始记录模块的修改日志,对于使用Git的我们来说,完全没用
- 归属与署名,源码控制系统会记录谁在何时添加了什么,所以也是没必要的。
- 注释掉的代码,直接把代码注释掉是讨厌的做法。删掉即可。
- HTML注释,源代码注释中的HTML标记是一种厌物。
第七章 错误处理
使用异常而非返回码
使用不可控异常,即不用声明throw
别返回null值,别传递null值
第九章 单元测试
TDD三定律
- 在编写不能通过的单元测试前,不可编写生产代码
- 只可编写刚好无法通过的单元测试,不能编译也算不通过
- 只可编写刚好足以通过当前失败测试的生产代码
保持测试代码整洁,三个要素:可读性,可读性和可读性。
整洁的测试遵循5条规则
-
快速(Fast)
- 独立(Independent)
- 可重复(Repeatable)
- 自足验证(Self-Validating)
- 及时(Timely),测试应及时编写,写生产代码之前。
第十章 类
类应当短小,只有少量的实体变量,保持类的内聚性。如果一个类的每个变量都被每个方法所使用,则该类具有最大的内聚性。内聚性强,类代码就会变短。
单一职责原则:类或模块应有且只有一条加以修改的理由。
隔离修改,对新增功能开放,对修改关闭。
依赖倒置原则,类应当依赖于抽象而不是具体细节。