代码整洁之道第——命名
第二章:命名
- 名副其实:通过名字可以读出变量的含义,如int daysSinceCreation
- 避免误导:特殊意义的词不能用,accountList也不要用(误导其是一个List,如果真的是一个List也无需这样,java是强对象的,所以不需要类型编码),不如accounts
- 有意义的区分:a1和a2就是无意义的区分,destination和source就是有意义的区分。
- 可读性好:不要使用自造词,如genymdhms、modificationTimestamp就是属于自造词。
- 使用可搜索的名称:单词字符名称如(a、e、i)仅用于短方法中的本地变量;其他变量、常量的命名长名称好于短名称,便于搜索。
- 避免使用编码:①不需要类型编码,错误示范:PhoneNumber phoneString,不如PhoneNumber string;②前缀无意义,成员变量_name或m_name不如直接用name;③接口实现,正确示范:UserService接口的实现UseServiceImp
- 避免思维映射:不应让变量名映射成读者脑中其他的含义
- 类名:类名和对象名应该是名字或名字短语,不能是动词,如Customer、AddressParser等,指代具体的事物,避免使用Data、Processor等。
- 方法名:动词或动词短语。如deletePage、save。
- 不要用俚语俗语,要言到意到。
- 每个概念对应一个词:如get和retrieve、controller和manager不要一起用(一个概念对应两个词了)
- 别用双关语:如insert比add更精确。
- 尽管用CS专业领域词汇,如thread processor
- 无法使用CS专业领域词汇,可以使用所涉及领域词汇(比如电商领域)。
-
- 添加有意义的语句 VS 不要添加没用的语境:一个类的几个成员变量,共同出现在了一个方法中,此时局部变量的命名可以加上类名前缀;但是在一个类中,成员变量的命名,没有必要加上类名前缀。
第三章:函数
- 短小:首要原则是短小,20行封顶最佳,函数的缩进层级(if while)不应该多余一层或两层。
- 只做一件事,做不到时,拆分新函数。
- 每个函数在一个抽象层级,比如Controller Service Dao就属于不同的层级,各层级的函数应该负责本层的业务,不能跨多级。
- switch语句:如果出现一次还能忍受,否则就用多态。
- 使用描述性函数名:长而具有描述性的名称,比短而费解的名称好,比注释好
- 函数参数:最理想的参数数量为0,其次是1,再次是2,尽量避免三个及以上参数;多参数导致测试组合几何倍增多。
①一元函数:参数有两个作用: 操作该参数(如 add(object)) 和 将参数转化为什么东西再输出(如 append(string))。
②标识参数:即向函数传入boolean值参数,极其不可取!可以将原函数一分为二。
③二元函数:尽量用一些机制将其转化为一元函数
④三元参数:尽量不写三元函数
⑤参数对象:参数有两个、三个或以上,说明应该考虑封装为类了。
⑥可变参数函数:可变参数函数有可能相当于一元函数、二元函数、三元函数。
⑦动词与名词:正确范例:assertExpectedEqualsActual(expected, actual)。函数名是动词,可以跟上名词对象,参数是名词。 - 无副作用:只做一下事,就是函数名字对应的事,如果不对应就可能会带来副作用; 避免把输入参数对象作为函数返回值,这样可以改造成void的无参函数。
- 分隔指令与询问:函数要么做什么事情,要么回答(查询、得到)什么事,二者不可得兼,如果都干,则将其拆分为两个函数。但是对于某些数据库的操作函数,就是二者都干的,比如insert,插入成功返回true,插入失败返回false。
- 使用异常代替返回错误码:将try代码块 catch代码块抽取成方法;错误处理的函数只做一件事,即try是函数的第一个词,catch finally后面不应再有内容;错误代码类是依赖磁铁,造成很其他类都要引入和使用,耦合严重,错误码修改使用它的地方都要重新修改编译,推荐使用异常代替错误码,新的异常可以从异常类派生出来。
- 重复代码时万恶之源
- 一个函数只有一个入口,但是可以有多个出口。
第四章:注解
- 注解不能美化代码,用代码函数命名阐明意图
- 好注释 VS 坏注释:
①没有规范每个函数都要有javadoc 和每个变量都要有注释
②注释掉的代码删掉即可。
③不要在代码中注释关于本地的信息,因为代码会部署到服务器
第五章:格式
- 格式的目的在于沟通,和修改维护的可读性
- 垂直距离:①研究了一些开源类库源码,表明类文件的长度大多数小于200,短文件比长文件更易于理解 ②不要把关系密切的概念放到不同的文件中,这也是避免使用protected变量的理由之一,③关联越密切,应该靠的越近,④调用者函数放在被调用者的上面。
- 风格要统一,保持团队规定风格。
第六章:对象和数据结构
- 数据抽象:私有成员;对外暴露越少越好
- 数据结构和对象:对象隐藏数据,暴露操作数据的函数;(面向过程代码)数据结构暴露数据,不提供函数
- The Law of Demeter:类C的方法f只应该调用一下对象的方法①C,类内的方法,②f创建的对象(局部对象)的方法,③作为参数传给f的对象,④C的实体变量持有的对象(类成员变量)
火车失事代码(如下),应拆分。
第七章:错误处理
- 使用异常而不是返回码,好处如下:
①上层直接catch异常即可,不必知道下层返回的结果。
②通过对错误处理方法的包装抽取,实现错误处理与业务算法隔离 - try-catch-finally
- 使用不可控异常:checked exception的依赖链成本很高。
- 给出异常发生的环境申明:在异常中一并记录失败的操作、失败的类型、失败操作初衷
- 定义异常类:很多catch1 catch2…等,如果实现了定义之后,多态来抽象,就可以变成只有一个catch
- 定义常规流程:不要用catch异常来处理业务逻辑,可以使用特例模式,创建一个类用来处理特例,也是多态的一种用法,就不用判断异常了,比如NullObject。
- 别返回null:返回null值必然带来的工作是检查null增加工作量,如果返回null,不如抛出异常,或者返回特例对象(如Collections.emptyList)。
- 别传递null:除非API要求,否则禁止传null。
第八章:边界
- 使用第三方代码、学习测试第三方代码、使用尚不存在的代码(分离)
第九章:单元测试
- TDD:测试先行,在编写生产代码前,先编写测试单元。
- 保持测试整洁:测试代码和生产代码一样重要,同样要保持整洁
- 测试代码最重要的是可读性,测试代码和生产代码的标准不同,测试代码不必关心性能优化。
- 每个测试一个断言:单个测试断言数量最小化;每个测试只测一件事
- 快速、独立、可重复、自验证、及时五大原则:快速要求不能运行缓慢;独立要求每个测试不依赖;可重复要求测试应该在任何环境下重复通过;自验证要求测试自己告诉成功或失败;及时要求测试先行,如果在生产代码之后编写测试的话,会难以测试。
第十章:类
- 类约定:JAVA约定类先从变量列表开始,先出现公共静态常量,然后出现私有静态变量,然后是私有实体变量,非常少有公共变量。工具函数在其调用者后面。
- 类短小的原则:
①单一权责(SRP):有且只有一条修改类的理由,多个短小的类而不是少个巨大的类
②高内聚:最大的内聚指:每个变量都被每个函数操作;保持内聚性的结果通常会得到很多短小的类。 - 为了修改而隔离:通过继承体系,实现对扩展开放,对修改关闭,依赖抽象而不依赖细节。
第十一章:系统
- 系统的创建和使用分开,也是单一职责的思想
- AOP三种:springAOP JBossAOP AspectJ
第十二章:迭进
- 共性抽取,消除重复的进一步做法。如发现违反了SRP原则,则应该把抽取的方法移到其他类中。
- 函数和类的数量要少,一些教条主义:为每个类创建接口、字段和行为一定要切分到字段类和行为类中,抵制这些教条。
- 整洁之道按照重要性排序(降序):测试、消除重复、表达力、类和函数少。
第十三章:并发编程
- 分离并发相关的代码和其他代码
- 严格显示对共享数据的访问
- 使用数据副本,避免共享数据。使用对象副本应该考虑的是:对象创建成本以及垃圾回收开销
- 并发问题的三种模型:生产者消费者模型、读者作者模型(读者读共享资源,作者偶尔更新共享资源)、哲学家进餐模型(共享资源数少于占用资源的人数)
- 装置代码,Object.wait() Object.sleep() Object.yeild() Object.priority() 改变代码执行顺序,以测试多线程可能出的问题
第十四章:逐步改进
第十五章:JUnit内幕
第十六章:重构
先运行,在优化。
第十七章:味道与启发
1、通常基类对派生类应该一无所知,例外情况是:派生类数量严格固定时(有限状态机),可以在基类中最派生类进行switch。
2、变量和函数应该在靠近被使用的地方定义,距离越短越好。
3、特性依恋:类的方法只应该对当前所属类的变量和函数感兴趣,不应该垂青其他类中的变量和函数。有时特性依恋是必要的。
示例:calculateWeeklyPay方法依赖了HourlyEmployee对象的特性。
4、不要在函数参数中带 boolean参数、枚举类型、整数或其他用于选择分类的参数,如果需要这样的情形,说明可以将大函数拆分为小函数。
5、把逻辑依赖改为物流依赖
一个模块依赖于另一个模块,不需要对被依赖模块进行假定,则是物流依赖。否则对依赖模块进行假定,则是逻辑依赖。
逻辑依赖,不利于程序变成和向下扩展。
6、switch语句:对于给定的选择类型,不应有多于一个switch语句,必须创建多态对象,消除其多个case。
7、用命名常量代替魔数,某些常量具有自解释能力,因此不必用命名常量去隐藏。
8、结构优于约定:继承是结构、代码常量命名是约定
9、避免使用否定性条件
10、显示表现出时序耦合,必要的时序不应该被隐蔽。可以通过上一个函数的返回结果,给下一个函数调用,以此实现前后调用的时序关系。
11、封装边界条件,用一个变量表示边界,变量的名显示边界的意义。
12、函数应该只在一个抽象层级上。比如一个函数通过一个对象A构建一个String,就只需要构建A,然后调A的toString类似方法即可,而不需要知道A的toString的细节。
13、避免传递浏览,即不要写a.getB().getC().doSomething()的代码,导致A与B耦合,且A与C耦合。正确的做法:b = a.getB(),b.doSomething(),这样A只需要了解B即可,b.doSomething()可以调用b.getC c.doSomething()
14、如果使用了一个包里的两个或多个类,可以使用import package.*通配符,避免导入语句过长
15、不要继承常量
完结。