代码整洁之道

来源:互联网 发布:大连软件交易会 编辑:程序博客网 时间:2024/06/09 14:06
第一章 整洁代码
    整洁代码的规则:
    ·能通过所有测试:
    ·没有重复代码;
    ·体现系统中的全部设计理念;
    ·包括尽量少的实体,比如类、方法、函数等
    在以上诸项中,我最在意代码重复,如果同一段代码反复出现,就表示某种想法耒在代码中得到良好的体现,我尽力去找出到底那是什么,然后再尽力曼清晰地表达出来。
在我看来,有意义的命名是体现表达力的一种方式,我往往会修改好几次才会定下名字来.借助Eclipse这样的现代编码工具,重命名代价极低,所以我无所顾忌.然而,表达力还
不只体现在命名上,我也会检查对象或方法是否想做的事太多,如果对象功能太多,最好是切分为两个或多个对象.如果方法功能太多,我总是使用抽取手段(Extract Method)重构之,从而得到一个能较为清晰地说明自身功能的方法,以及另外数个说明如何实现这些功能的方法,消除重复和提高表速力让我在整洁代码方面获益良多,只要铭记这两点,改进脏代码时就会大有不同.不过,我时常关注的另—规则就不太好解释了。
    这么多年下来,我发现所有程序都由极为相似的元素构成。例如“在集合中查找某物”。不管是雇员记录敷据库还是名值对哈希表,或者某类条目的数组,我们都会发现自己想要从集合中找到某一特定条目.一旦出现这种情况,我通常会把实现手段封装到重抽象的方法或类中.这样做好处多多,可以先用某种简单的手段,比如哈希表来实现这一功能,由于对搜索功能的引用指向了我那个小小的抽象,就能随需应变,修改实现手段.这样就既能快速前进,又能为未来的修改预留余地。
    另外,谊集合抽象常常提醒我留意“真正”在发生的事,避免随意实现集合行为,因为我真正需要的不过是某种简单的查找手段.
    战少重复代码,提高表达力,提早构建简单抽象.这就是我写整洁代码的方法


第二章  有意义的命名
2.2 名副其实
1    变量名不要废话的字符,比如studentList(不如students)
2    每个变量名代表什么意思,比如:
...
if(x[0]==4){//这里4是什么意思
...
}

2.4 做有意义的区分
public void copyChars(char a1[],char a2[])
这里参数名改为source和destination好很多
另外,像Info和Data一样都是无意义的废话,Table不应出现在表名中,Variable不应出现在变量名中。
2.5 使用读的出来的名字
2.7 避免使用编码:
2.7.2 成员前缀
很多时候人们会无视前缀或后缀,只看到名称中有意义的部分,最终前缀变成了废物。
2.7.3 接口和实现
不应让使用者知道调用的是接口,所以接口名最好别标注,比如LogService不应写为ILogService;
接口没标注所以实现就要标注了:LogServiceImp

2.9 类名
类名和对象名应该是名词或名词短语
类名和对象名应该是名词或名词短语,如Customer、wikiPage、Account和AddressParser.
避免使用Manager、Processor、Data或Info这样的类名。类名不应当是动词。
2.10 方法名
方法名应该是动词或者动词短语
2.12 每个概念对应一个词
比如controller,Service,dao
2.17 不要添加没用的语境
比如没用的缩写前缀

第三章   函数
3.1 短小
函数的缩进层最好不多于一层或两层
3.2 只做一件事
要判断函数是否不止做了一件事,还有一个方法就是看是否能再拆分出一个函数,该函数不仅只是单纯的重新诠释其实现
3.3 每个函数一个抽象级别
自顶向下读代码:向下规则:要怎样做A,则要做B,要做B,则要先做C。不断引出函数
3.4 switch语句
使用switch时候很容易违反一些原则:比如只做一件事,单一原则,开放封闭原则等。解决方法可以把语句抽象到抽象工厂下。
3.5 使用描述性的名词
比如:testableHtml 改为SetupTeardownIncluder这个名词好多。函数越短小功能越集中就越便于取个好名字
3.6 函数参数
数量最理想是0,然后是1,再次是2,尽量避免3个参数
3.6.1 一元函数
如果要对输入参数进行转换操作,转换结果就该体现为返回值。StringBuffer transform(StringBuffer in)比void transform(StringBuffer in)强。
如果是事件,有输入参数无输出参数(void),用参数来修改系统状态,则要让读者清楚的了解他是个事件。
3.6.2 标识参数
向函数传入布尔参数是骇人听闻的做法。render(true)对可怜的读者来说会很迷糊,查看API看到render(Boolean isSuite)稍许有帮助,不过仍不够,应该把函数一分为二:renderForSuite()和renderForSingleTest()
3.6.3 二元函数
有些时候两参数正好,比如点new Point(0,0)就相当合理,但多数二元函数比一元要难理解。
writeField(outputStream,name) 可以优化为:outputStream.writeField(name),或者也可以把outputStream写成当前类的成员变量,从而无需再传递他。还可以分离出类似FieldWriter的新类,在其构造器中采用outputStream,并包含一个write方法。
assertEquals(expected,actual)可以优化为:assertExpectedEqualActual(expected,actual)
3.6.4 三元函数
要比2元函数难懂得多。尽量少用
3.6.5 参数对象
如果函数需要2,3或以上的参数,就说明应该把参数封装为类了。如:
Circle makeCircle(double x, double y, double radius);
Circle makeCircl'e(Point center, double radius):
从参数创建对象,从而减少参数数量,看起来像是在作弊,但实则并非如此。当一组参数被共同传递,就像上例中的x和y那样,往往就是该有自己名称的某个概念的一部分
3.6.7动词与关键字
对于一元函数,函数和参数应当行程一种非常良好的动词/名词对形式。比如 wirte(name)。更好地名大概是writeField(name),他告诉我们name是一个field。
再比如,assertEqual(axpected,actual)改成assertExpectedEqualsActual(axpected,actual)可能会好些,减轻我们记忆负担
3.7无副作用
比如,对自己类中的变量做出未能预期的改动;会把变量搞成向函数传递的参数或是系统全局变量。
简单说就是函数只做一件事,不对外部的事情做处理。如果处理了则要在函数名告知使用者。
输出参数
很多时候会被输出参数迷惑。尽量少用。
public void appendFooter(StringBuffer report)
最好换成这样调用:
report.appendFooter();
普遍而言,应避免使用输出参数。如果函数必须要修改某种状态,就修改所属对象的状态吧。
3.8函数要么做什么事,要么回答什么事,但二者不可得兼。
比如:public boolean set(String attribut,String value)判断是否存在并设置值。
这里既有设置属性,又有判断并返回值,容易让人迷惑。
真正的解决方案是把指令与询问分开来:
if(attributeExist("username")){
    setAttribute("username","unclebob");
}
3.9使用异常代替返回错误码
3.9.1 抽离try/catch代码块
尽量把try中的逻辑封装为函数来调用
3.9.2 错误处理就是一件事,和函数一样。
3.10别重复自己
3.13小结
函数是语言的动词,类是名词。



第4章  注释
注释应该想方设法用代码来表达
因为注释也需要维护,而有时候会只维护代码而忘记维护了注释。
不准确的注释比没注释坏的多。
4.1 注释不能美化糟糕的代码
4.2 用代码来阐释
4.3 好注释
4.3.2 提供信息的注释
比如解释函数的返回值。
尽量用函数名来表达信息
4.3.3 对意图的解释
为什么这么写代码
4.3.4 阐释
解释难懂代码的意义:注意会冒着注释本身会有不正确的风险
4.3.5 警示
比如警告某条代码会造成什么后果
4.3.6 TODO注释
注意不要留着不改,当做减少工作的借口
4.3.7 放大
可以放大某些看起来不重要的地方
4.3.8公共API中的javadoc

4.4 坏注释
4.4.1 提供别人看不懂的注释
4.4.2 多余的注释:尽量用函数名来代替注释;尽量禁烧八股文式的注释
4.4.3 误导性注释
4.4.4 循规式注释,和4.4.2类似
4.4.5 日志式注释
4.4.5 废话注释
4.4.8 能用函数或变量时就别用注释
4.4.9 位置标识  比如  ****************
4.4.10 括号后面的注释,为了看清代码块哪里结束
比如
try{
...
}//try 
catch(){
...
}//catch 
解决方法应该是缩短函数
4.4.11 归属于署名:由源码控制系统来记住吧。
4.4.12 注释掉的代码:会让后人摸不清头脑,如果是为了自己参考的话可以从源码控制系统里面查看就行,没有留的理由。
4.4.13 HTML注释
易于导出,但是尽量用工具来处理格式吧
4.4.14 非本地信息
尽量确保描述与他最近的代码。别人的代码无法保证。
4.4.15 信息过多:不需要写不相关的信息
4.4.16 不明显的联系
4.4.17 函数头:短函数不需太多描述,为只做一件事的短函数选个好名字,通常比写函数头注释要好。
4.4.18 非公共代码的javadoc
虽然javadoc对于公共API很用用,但不让打算用作公共的代码就令人厌恶了。会几乎等同于八股文

第5章 格式
5.2 垂直格式
5.2.1 向报纸学习
5.2.2 概念间垂直方向上的隔离
5.2.3 垂直方向上的靠近:暗示他们的紧密关系
5.2.4 垂直距离
变量声明。应该靠近其使用位置。
实体变量。类的顶部
相关函数。如果某个函数调用了另外一个,就应该按调用顺序放在一起。
相关概念。相关概念的代码应该放在一起。
5.3 横向格式
5.3.1 水平方向上的隔离与靠近
赋值符号两边空格增强分隔效果
不在函数名和左圆括号之间加空格,因为函数与参数密切相关。括号中的参数用空格隔开,强调参数是互相分离的。
如: public root(double a, double , double c){...
优先级也可以加空格强调:
return (-b + Math.sqrt(determinant)) / (2*a);
5.3.2 水平对齐:没什么用
5.3.4 空范围
while和for有时语句体为空,尽量少用空结构。如果无法避免就用空括号包围起来,或者隔行加分号
while(xxx)
;
5.4 团队规则:每人有每人的偏爱,但团队一定要由规则。

6 对象和数据结构
6.1 数据结构
类通过get set 方法将其变量往外推,如果控制不好也是对外暴露数据结构。所以当不愿暴露数据结构的时候,简单添加get set 是最坏的选择。
6.2 数据、对象的反对称性
过程式代码:

面向对象式代码:

过程式代码便于在不改动既有数据结构的前提下添加新函数,面向对象代码便于在不改动有函数的前提下添加新类。
也就是:
过程式代码难以添加新数据结构,因为必须修改所有函数,面向对象代码难以添加新函数,因为要修改所有类。

6.3 德墨忒尔律:
模块不应了解它所操作对象的内部情形。也就是隐藏数据,暴露操作,所以就不能通过get set 方法暴露内部结构。
也就是:类C 方法f 只应该调用以下对象的方法:
C
由f创建的对象
作为参数传递给f的对象
由C的实体变量持有的对象
6.3.1 数据结构只简单的拥有公共变量,没有函数,而对象则拥有私有变量和公共函数。
6.3.2 有时候无可避免一半是对象一半是数据结构。无论怎样,都避免公共get诱导外部函数以过程式程序使用数据结构的方式使用这些变量。就是别盲目为变量加get set 方法。
6.3.3 隐藏结构:对外面隐藏内部的结构
6.4 数据传送对象 DTO
不要DTO塞业务规则方法。

第7章  错误处理
7.1 使用异常而非返回码,比如把异常捕获后返回-1等。
7.2 先写try chtch finally 语句
try代码块就像事务,catch块将程序维持在一个持续状态,无论try代码块中发生了什么均如此。所以在编写可能抛出异常的代码时,最好先写出try-catch-finally语句,能帮你定义代码的用户应该期待什么,无论try代码块中执行的代码出什么错都一样。
7.3 使用不可控异常。
如果抛出可控异常,就得在catch语句与抛出异常处之间的每个方法签名中声明该异常。那么底层修改都将波及到高层签名。
7.4 给出异常发生的环境说明
抛出的每个异常都应该提供足够的环境说明,以便判断错误的来源和处所。
应创建充分的错误信息和异常一起传递出去。
7.6 定义常规流程
有些外部API是可控异常必须捕获。可以打包这些API的异常,处理了异常,在提供给外部调用。
7.7 别返回null值
返回null值基本是给自己增加工作量,给调用者添加麻烦。每次调用都要判断是否为空,这样代码不整洁还容易忽略或出错。
7.8 别传递null值
把null值传递给其他方法很糟糕,除非API要求你向他传递null值


第8章 边界
8.1 使用第三方代码
有时候的对于边界接口包装一下会比较好
8.4 学习性测试的好处不只是免费
学习性的测试能帮助我们增进对API的理解
8.6 整洁的边界
边界上的代码需要清晰的分割和定义了期望的测试。应该避免我们的代码过得了解第三方代码中的特定信息。依靠你能控制的东西,好过依靠你控制不了的东西,避免日后受他控制。
一个好方法就是对第三方代码进行包装。

第9张 单元测试
9.1 在编写不能通过的单元测试前,不可编写生产代码
9.2 保持测试整洁
测试代码和生产代码一样重要。他需要被思考被设计和被照料,他该像生产代码一样保持整洁。
9.3 整洁的测试
要素:可读性,和其他代码一样,明确,简介,还有足够的表达力
9.4 每个测试一个断言
每个单元测试有且只有一个断言语句。也不要害怕多个断言,但是断言数量应该最小化
每个测试一个概念
不要一个测试中测试这个又测试那个。
9.5 FIRST 
快速 fast 测试应该能快速运行
独立 independent 测试应该相互独立
可重复性 repeatable 测试应当热可在任何环境中重复通过
自足验证 self-validating 测试应该有布尔值输出。无论通过或失败,都不应该是查看日志来判断是否哦他能够过
及时 timely  测试应该及时编写
9.6 如果你坐视测试腐坏,那么代码也会跟着腐坏。

手工测试应该是最后的测试,因为这种测试效率低,无法覆盖全,无法重复测试。单元测试应该在手工测试之前。

第10章 类
10.2 类应该短小
类名应当描述其权责。如果无法为某个类以精确的名称,这个类大概太长了。
10.2.1 
系统应该由许多短小的类而不是少量巨大的类组成。每个小类封装成一个权责,只有一个修改的原因,并与少数其他类一起协同达成期望的系统行为。
10.2.2 内聚
类应该只有少量实体变量。类中的每个方法都应该操作一个或多个这种变量。
保持函数和参数列表短小的策略,有时候会导致为一组子集方法所用的实体变量数量增加,出现这种情况时候,往往意味着要分拆类,让新类有更高的内聚性。
10.2.3 保持内聚性就会得到许多短小的类
想想看一个有许多变量的大函数,你想把该函数中某一小部分拆解成为单独的函数,不过你想要拆出来的代码使用了该函数中的4个变量,是否必须将这4个变量都作为参数传递到新函数中去呢?
完全没必要,只需要将这4个变量提升为类的实体变量,完全无需传递任何变量就能拆解代码了。可惜这让类堆积了越来越多只为允许少量函数共享而存在的实体变量,降低内聚性。所以,把这些共享的函数抽出来作为独立的类,这是个好的方法。

第11章 系统
11.2 将系统的构造与使用分开
假如new了一个对象,那么不分解这些依赖关系就无法编译,即便在运行时用不使用这种类型的对象;
而且测试也是个问题,我们要确保在单元测试调用该方法之前,就指派了恰当的测试替身(test double)或仿制对象(mock) ,如果构造逻辑复杂,那么要测试所有的执行路径。
最糟糕的是还不知道NEW出的是否正确对象。

第12章 跌进
12.1 通过跌进设计达到整洁目的
简单设计4原则:
运行所有测试;
不可重复;
表达了程序员的意图;
尽可能减少类和方法的数量
12.2 运行所有测试
只要系统可测试,就会导向保持类短小并且目的单一的设计方案。
紧耦合代码难以编写测试。
遵循有关编写测试并持续运行测试的简单,明确的规则,系统会更低耦合高内聚。编写测试引导更好的设计
12.3 重构
测试消除了对清理代码就会破坏代码的恐惧。有了测试能方便重构。
12.4 不可重复
12.5 表达力
通过好名称来表达。
也可包吃函数和类短小来表达。
还可以通过采用标准命名法来表达。比如..VISITOR ,..Controller 就能充分表达你的意思
编写良好单元测试也有表达性。
12.6 尽可能减少的类和方法
某些教条主义会导致不必要的类和方法出现,要抵制。




























0 0
原创粉丝点击