第一章
整洁代码的必要性和不同人对整洁代码的定义
第二章 (有意义的命名)
变量、函数、参数、类、封装包、源代码及源代码所在的目录、jar文件、war文件等等。
名副其实
如果名称需要注释来补充,那就不是名副其实。
避免误导
比如某些系统或者语言的预留关键字,或者选小写字母L和大写字母O,特别是组合使用想是常量壹和零。
做有意义的区分
比如a1、a2、a3…aN没有表名作者的意图用具体的参数名称比如source、destination会更好;
废话是另一种没有意义的区分,比如Product类、ProductInfo类、ProductData类,名称不同意义却无区别。info,data就像a
、an、the一样是意义含混的废话,Variable一词永远不要出现在变量名中。对比:NameString和Name,Custom类和CustomerObject类,money和moneyAmount,message和theMessage也没有区别。
使用读的出来的名称
就是正常的能念出来的名词,别是什么不通顺的 “皮埃死极翘” 整数
使用可搜索的名称
数字7不好找,但是常量MAX_CLASSES_PRE_STUDENT就好找,同样字母e也不好找,单字母名称仅用于短方法中的本地变量。名称长短应与其作用域大小相对应。若变量或常量多处使用,应便于搜索。
避免使用编码
匈牙利语标记法
增加阅读难度 ,BASIC早期版一个字母加上一个数字,Fortran首字母体现类型;
成员前缀
m_前缀等,人们很快学会无视前缀只看到名称中有意义的部分。
接口和实现
IShapeFactory和ShapeFactory 首字母I是干扰,改为ShapeFactory和ShapeFactoryImpl更好。
避免思维映射
不应当让读者在脑中把你的名称翻译为他们熟知的名称,常出现在是使用问题领域术语还是解决方案领域术语。明确是王道。
类名
类名和对象名称应该是名词或名词短语
如Customer、WikiPage、Account、AddressParser。避免使用Manager、Processor、Data、Info这样的类名,不应该是动词。
方法名
方法名应该是动词或动词短语。
如postPayment、deletePage、save。访问属性应该根基javabean标准加上get、set、is前缀。
可以考虑将相应的构造器设置为private,强制使用这种命名手段。
最好是类名加方法名是个完整的语义。
别扮可爱
宁可明确,毋为好玩,言到意到,意到言到。俚语,俗语等不可取。
每个概念对应一个词
fetch、retrieve和get是一个概念,不同的类中相同概念的方法用同一个词。controller,manager,driver是一个概念还是不同的概念?所以,一以贯之地命名吧。
别用双关语
不要同一单词用于不同目的,应遵循一词一义。多个类中有add方法,可以,但是这时有个类名是add就不妥。insert,append都可替换。同一术语用于不同概念就是双关。
使用解决方案领域名称
毕竟只有程序员才读代码。计算机科学术语、算法名、模式名、数学术语等。
使用源自所涉问题领域名称
如果不能使用程序员熟悉的术语命名就用所涉及问题领域的名称。
添加有意义的语境
多数名称不能自我说明,需要良好命名的类、函数或名称空间来给读者提供语境,实在不行,给名称添加前缀是最后一招。
不要添加无用的语境
在某个应用(GSD)里给每个类添加一样的GSD前缀就不好。只要短名称足够清楚,就比长名称好,别给名称添加不必要的语境。
总结
我们把代码写的就像词句篇章、至少是表和数据机构(词句并非总是呈现数据的最佳手段)。
第三章(函数)
短小
每个函数只说一件事,三四行,依序待到下一个函数。if、else、while语句等,其中的代码块应只有一行函数调用语句。这样保证函数短小且块内调用的函数拥有较具说明性的名称,增加文档的价值。所以函数的缩进层级不该对于一层或两层,抑郁阅读和理解。
只做一件事
做好这件事,只做这一件事。看能否再拆出一个函数。只做一件事的函数无法被合理地切分为多个区段。
每个函数一个抽象层级
要确保只做一件事,函数中的语句都要在同一抽象层级上。自订向下读代码,让每个函数后面跟着位于下一抽象层级的函数,这样查看函数列表就能循抽象层级向下阅读。就像一系列TO起头的段落,每一段都面熟当前抽象层级,并引用位于下一抽象层级的后续TO起头段落。
switch语句
短小的switch很难,所以确保switch埋藏在较低的抽象层级,而且永远不重复。
使用描述性的名称
长而具有描述性的名称比短而让人费解的名称好,比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说清其功用的名称。
函数参数
理想的参数数量是零参,其次是一,再次是二,应避免三。有足够的理由才能用三个以上参数。
一元函数的普遍形式
1、转换:可能是操作改参数,将其转换为其他东西再输出返回值。
2、事件:有输入参数无输出参数,使用该参数修改系统状态。
标识参数
向函数传入布尔值简直是骇人听闻。表明该函数不止做一件事,应该把函数一分为二。
二元函数
不算恶劣,也得会编写,尽量利用一些机制转换一元函数。
三元函数
并不险恶,需要费点神,有时候也值得。
参数对象
如果需要两个、三个、或以上参数,说明其中一些参数应该封装为类。
参数列表
有时要向函数传入数量可变的参数。可变参数的函数可能是一元、二元、甚至三元。
动词与关键字
对于一元函数,函数和参数应当形成一种非常良好的动词/名词对形式。比如write(name)或者writeField(name)。
二元函数assertExpectedEqualsActual(expected,actual)。
无副作用
承诺只做一件事,但还是会做其他被藏起来的事。会对自己类中的变量做出未能预期的改动,或者把变量搞成反向函数传递的参数或是系统全局变量,导致时序性耦合及顺序依赖。应避免使用输出参数。如果函数必须要修改某种状态,就修改所属对象的状态吧。
------不理解可看书上举例
分割指令与询问。
函数要么做什么事,要么回答什么事,二者不可兼得。函数应该修改某对象的状态,或返回改对象的有关信息。两样都干会导致混乱。真正的解决方案是把指令与询问分隔开来。
使用异常替代返回错误码
***指令式函数***返回错误码违反了指令与询问分隔的规则,鼓励了在if语句判断中把指令当表达式使用,导致更深层次的嵌套结构,返回错误码就是要求调用者立刻处理错误了。如果使用异常替代,错误处理代码就能从主路径代码中分离出来,得到简化
抽离try/catch代码块
try/catch代码块会搞乱代码结构,把错误处理与正常流程混为一谈。最好把try/catch代码块抽离出来,另外形成函数。
错误处理就是一件事
错误处理的函数不该做其他事。
Error.java依赖磁铁
错误码意味着有个类或者枚举,定义了所有的错误码。这个类就是一块依赖磁铁,修改时需要重新便已部署,对Error类造成了负面压力。使用异常替代错误码,新异常可以从异常类派生出来,无需重新编译或重新部署。
别重复自己
造成代码臃肿,修改时需要多处修改。消除重复提高可读性。
结构化编程
每个函数,函数中的每个代码块都应该有一个入口一个出口,只有一个return,循环中不能有break和continue语句,永远不能有goto语句。但对于小函数这些规则助益不大,函数短小,偶尔出现return、break和continue没有坏处,比单入单出更具表达力。goto只在大函数中才有道理。
如何写出这样的函数
一开始就写冗长复杂,太多缩进,嵌套循环。写完遵从这些规则就打磨改吧。
我还是觉得如果一开始能抽取出来就抽取,不过后面可能也得调整,因为没写完可能存在考虑不周的情况。
第四章(注释)
注释的恰当使用是弥补我们用代码表达意图时遭遇的失败。注释总是一总失败,因为注释会撒谎。尽管有时也需要注释,但是应该尽量减少注释量。
注释不能美化糟糕的代码
与其花时间编写解释你搞出的代码的注释,不如花时间清洁那堆糟糕的代码。
用代码来阐述
用代码解释你大部分的意图,很多时候,简单到只需要创建一个与注释所言同一事物的函数即可。
好注释
法律信息
比如:版权及著作权声明。这类注释尽量指向一份标准许可或其他外部文档,而不要把所有条款放到注释中。
提供信息的注释
比如,解释某个抽象方法的返回值,或者正则表达式的解释等。
对意图的解释
注释不仅提供有关实现的有用信息,还提供某个决定后面的意图。
阐述
有时,注释把某些晦涩难懂的参数或返回值的意义翻译为某种可读形式,也是有用的。通常,更好的方法是尽量让参数或返回值自身就足够清楚;但是如果参数或返回值是某个标准库的一部分,或是你不能修改的代码,帮助阐述其含义就会有用。
警示
用于警示其他程序员会出现某种后果的注释也是有用的。
TODO注释
用//TODO形式在源码中放置要做的工作列表。是一种程序员认为应该做,但由于某些原因目前还没做的工作。
放大
注释可以用来放大某种看起来不合理之物的重要性。
公共API中的Javadoc
没有什么比被良好描述的公共API更有用和令人满意了。如果你在编写公共API,就该为它编写良好的Javadoc
坏注释
大多数注释都属于此类。通常,坏注释都是糟糕的代码的支撑或借口,或者对错误决策的修正,基本上等于程序员的自说自话。
喃喃自语
如果只是你觉得应该或者因为过程需要就添加注释,那就是无谓之举。
多余的注释
并不能比代码本身提供更多的信息。
误导性注释
会导致代码的错误调用。
循规式注释
所谓每个函数都要有Javadoc或每个变量都要有注释的规矩是愚蠢可笑的。
日志式注释
有人会在每次编辑代码时,在模块开始处添加一条注释。这种注释就像是一种记录每次修改的日志。会让模块凌乱不堪。
废话式注释
用整理代码的决心替代创造废话的冲动吧。
可怕的废话
javadoc里也可能有废话。
能用函数或变量时就别用注释
重构代码呗。
位置标记
比如 //Actions ///
括号后面的注释
带来混乱 {
}//while 等
归属与署名
/added by ee/
源码控制系统是这类信息最好的归属地。
注释掉的代码
注释掉的代码以后可能有用,但是我们有源码控制系统啊,比如Git。
html注释
源代码注释中的HTML标记是一种厌物。
最好是抽取出来,展示到网页
非本地信息
请确保注释描述了离它最近的代码。
信息过多
别在注释中添加有趣的历史性话题或无关的细节描述。
不明显的关系
注释及其描述的代码之间的联系应该显而易见。
函数头
短函数不需要太多描述。
非公共代码中的Javadoc
虽然Javadoc对于公共API有用,但是非公共用途就让人厌恶了。
第五章(格式)
底层代码保持整洁,团队应一致统一采用一套简单的格式规则。
格式的目的
代码功能会被修改,但可读性却对修改、扩展、维护产生影响。
垂直格式
向报纸学习
从上往下,从左往右读。名称应当简单且一目了然。源文件最顶部应该给出高层次概念和算法。细节应该往下渐次展开,知道找到源文件中最底层的函数和细节。
概念间垂直方向上的区隔
每行展现一个表达式或一个句子,每组代码行展示一条完整的思路。这些思路用空白行区隔开来。
垂直方向上的靠近
空白行隔开了概念,那么靠近的代码行则暗示了它们之间的紧密关系。所以紧密相关的代码应该互相靠近。
垂直距离
变量声明应尽可能靠近其使用位置。
实体变量应该在类的顶部声明。
相关函数(某个函数调用了另一个)放一起。
概念相关的代码应该放一起。
垂直顺序
自上向下展示函数调用依赖顺序,被调用的函数应该放在执行调用的函数下面。
横向格式
应尽力保持代码行短小。
水平方向上的区隔与靠近
使用空格字符串将彼此紧密相关的事物连接到一起,也用空格字符把相关性弱的事物分隔开。赋值操作符周围加上空格字符,达到强调目的。不在函数名和左圆括号之间加空格。这是因为函数与其参数密切相关。函数调用括号中的参数一一隔开,强调逗号,表示参数互相分离。空格也可以强调其前面的运算符。乘法因子之间没加空格,因为他们具有较高优先级,加减法运算项之间用空格隔开,因为加减法优先级低。
水平对齐
缩进
文件顶层的类说明,不缩进;类中的方法相对该类缩进一个层级;方法的实现相对方法声明缩进一个层级;代码块的实现相对于其容器代码块缩进一个层级。
空范围
有时,while或for语句的语句体为空,如果无法避免,就确保空范围体的缩进,用大括号包围起来加分号。
团队规则
每个程序员都有自己喜欢的格式规则,但如果在一个团队中工作,就是团队就是算。一组开发者应当认同一种风格。
第六章(对象和数据结构)
将变量设为私有是我们不想其他人依赖这些变量,还想能自由修改类型或实现。那么对对象自动添加赋值器和取值器就将私有变量公之于众,如同公共变量一般。
数据抽象
隐藏实现并非只是在变量之间放一个函数层那么简单,而是抽象。类并不简单地用取值器和赋值器将其变量推向外间,而是暴露抽象接口,以便用户无需了解数据的实现就能操作数据本体。
数据、对象的反对称性
对象把数据隐藏于抽象之后、暴露操作数据的函数。数据结构暴露其数据,没有提供有意义的函数。
过程是代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类。在任何一个复杂系统中,都会有需要添加新数据类型而不是新函数的时候。这时,对象和面向对象就比较合适。另一方面,也会有想要添加新函数而不是数据类型的时候。这种情况下,过程式代码和数据结构更合适。
德墨忒耳律
模块不应该了解它所操作对象的内部情形。类C的方法f只应该调用以下对象的方法:
- C
- 由f创建的对象
- 作为参数传递给f的对象
- 由C的实体变量持有的对象
火车失事
连串的调用:
final String outputDir = ctxt.getOptions().getAcratchDir().getAbsolutePath();违反了德墨忒尔定律。下面的就不会:final String outputDir = ctxt.options.acratchDir.absolutePath;
混杂
一半是对象,一半是数据结构。这种结构拥有执行操作的函数,也有公共变量或公共访问器及改值器。公共改值器及访问器把私有变量公开化,诱导外部函数以过程式程序使用数据结构的方式使用这些变量。
隐藏结构
防止当前函数因浏览它不该知道的对象而违反德墨忒尔律。
数据传送对象
最为精炼的数据结构,是一个只有公共变量、没有函数的类。这种数据结构有事被称为数据传送对象,或DTO。是非常有用的结构,尤其是在与数据库通信、或解析套接字传递的消息之类场景中。
总结
对象暴露行为,影藏数据。便于添加新对象类型而无需修改既有行为,同时也难以在既有对象中添加新行为。数据结构暴露数据,没有明显的行为。便于向既有数据结构添加新行为,同时也难以向既有函数添加新数据结构。
第七章(错误处理)
当错误发生时,程序员有责任确保代码照常工作。凌乱的错误处理代码,如果搞乱了代码逻辑,就是错误的做法。
使用异常而非返回码
返回码需要在调用之后即刻检查错误,容易被遗忘。
先写Try-Catch-Finally语句
先构造try代码块的事务范围,而且也会帮助你维护好该范围的事务特征。执行try-catch-finally语句中try部分代码时,你是在表明可以随时取消执行,并在catch语句中继续。
使用不可控异常
(๑•ᴗ•๑) 可控和不可控的区别?
给出异常发生的环境说明
你抛出的每个异常,都应当提供足够的环境说明,以便判断错误的来源和处所。Java中可以从异常里得到堆栈踪迹,然而无法告诉你失败操作的初衷。应创建充分的错误消息,包括失败的操作和类型。传递足够的信息给catch块,记录下来。
依调用者需要定义异常类
错误分类可以依来源分类:是组件还是其它地方,或依类型分类:是设备错误、网络错误还是编程错误。最重要的考虑应该是它们如何被捕获。
定义常规流程
有时我们不想打包外部API以抛出自己的异常。特例模式:创建一个类或配置一个对象,用来处理特例。你来处理特例,客户代码就不用应付异常行为了。异常行为被封装到特例对象中。
别返回null值
过多检查null值的应用程序,坏透了,返回null值是在给自己增加工作量,也是在给调用者添乱。只要有一处没检查null值,应用程序就会失控,NullPointerException。如果你打算在方法中返回null值,不如抛出异常,或者返回特例对象。比如使用Collection.emptyList()方法返回一个预定义不可变列表,代替返回null,也就避免了空指针异常,代码也整洁了。
别传递null值
在方法中返回null值是糟糕的做法,但将null值传递给其他方法就更糟糕了。除非API要求你向它传递null值,否则就要尽可能避免传递null值。