![511c91bf04ae02d9986cb9e67f0d1706.png](https://img-blog.csdnimg.cn/img_convert/511c91bf04ae02d9986cb9e67f0d1706.png)
1.代码整洁
- 1.1. 有意义的命名
- 1.2. 函数
- 1.3. 注释
- 1.4. 数据,对象的反对称性
- 1.5. 错误处理
- 1.6. 类
- 1.7. 好的代码示范
- 2.重构
- 2.0.1. 重构的过程
- 2.0.2. 重构的原则
- 2.0.3. 什么情况下重构
- 2.0.4. 构筑测试体系
- 2.0.5. 重构手法目录
代码整洁规范
减少重复代码
提高表达力
提早构建简单抽象
代码整洁
有意义的命名
名副其实
- 如果你需要注释来解释一个变量,那就说明它不是名副其实
- 一旦发现有更好的命名,就要换掉旧的,要记住换花的时间是值得的
避免误导
- 尽量少使用类型名,除非它真的是这个类型
- 不要使用过于相似的名字
命名规范
- 类名和对象是名词,方法是动词,注意get/set/is的前缀
- 即每个抽象概念选一个词,DeviceManager和Protocal-Controller是一样的
- 要保持前后术语规范。
- 用常量代替原始数字(魔术数)
- 不要在代码中重复写+1的边界条件,而是用变量进行封装
- 函数的长短与其作用范围息息相关,所以for循环可以用i,j因为它们作用域很小
函数
函数要做到自己理解,而不是通过测试用例,这需要改进和重构简洁。
尽量短小
- if/for/while中应该只有一行
- 函数只做一件事,抽象层上应该是同一级别!
- 函数的顺序,是自顶向下的规则,抽象到具体的顺序
参数
- 参数最多不应该超过3个,如果参数多于3个的时候,就可以思考参数是否能构成一个类,参数过多的时候可以组合成字典传递
- 参数不要传布尔值,应该将true/false分成两个函数,然后在布尔值的上一个抽象层直接做判断,而不是将其作为布尔值传递。(整数值,枚举元素等函数选择行为同理)
- 参数不要传字面值,应传变量
- 最好保证对象的拷贝性,统一拷贝而不是在原值上修改
返回值
- 可以定义这样的规范,每个函数的返回值都叫result
处理异常
- try/catch的每个功能都应该拆出成一个函数
- 如果你用枚举类型来表示异常,那将意味着你会很不情愿修改它,使用异常代替错误码,新异常就可以从异常类中派生出来.
重复
- 永远无法容忍重复代码,这意味着抽象的遗漏
- 如果你发现死代码(永不执行的代码块),果断删除
- 多个switch/ifelse,考虑多态
判断
- if的条件如果过多,就要封一个函数,因为没有上下文是看不懂if的
- 避免否定条件,尽量保持一致,不要带!,可以直接把!函数封成函数
静态
- 如果一个函数不依赖其类的属性,都是依赖于参数,这时候该函数就应该声明为静态
- 静态导入(就是单纯导入),而不是用继承的方式,这样别人不知道函数的来源
变量
个人喜欢在第一次需要使用变量的地方声明,而不是在顶部一口去声明
注释
尽量少写注释
- 写注释意味着你的代码不够优雅,别人无法理解
- 能用函数和变量拆解的时候就拆解,而不是用注释去描述
- 你无法始终坚持维护你的注释,会出现各种问题
- 不要注释代码,我们有优秀的代码管理器,无用的代码就删掉就可以了,它会保留的。
好的注释
- 法律与版本信息
- 某个决定背后的意图解释,便于后续别人的修改
- TODO
如何写注释
- 一旦写注释就要花时间写好注释,注释的角度尽量从别人的角度出来来想会有什么疑惑,而不是解决自己的疑惑
- 不要尝试描述代码本身
数据,对象的反对称性
抽象
- 隐藏实现并非在变量之间放上一层函数层,也不是单纯使用取值器和赋值器往外推,而是暴露抽象接口,以便于用户无需了解数据形式就可以操作数据。
- 过程式代码便于添加函数(switch实现);对象类代码便于添加数据类型(多态实现)
- DTO(data transfer object),豆结构,即只有赋值器和取值器操作的私有变量
错误处理
try-catch
- try-catch: 一开始可以用exception捕捉,捕捉到之后要缩小到对应范围。
- try的内容是一个原子事务,这一点要注意想清楚。
NULL值处理
- 绝对不要返回Null值,否则别人检查null值会很麻烦,而且检查一多就会乱(可以内部直接抛出错误,也可以定义为空值就好)
- 禁止参数传入Null值,否则你总不可能每个变量一个个检查null(如果实在没有办法就只能用断言assert检查一下吧,用if有时会太乱混淆)
类
短小
- 类的名称要描述其权责而不能是一些模糊的词(Process,manger,super),而且描述该类职责的时候不应该出现if/and/or/but这些词语
- 类的权责应该是单一的,尽量拆成多个短小类吧
- 长的函数拆成小的函数的时候,参数是否要传,若是那为什么不把它们变成一个类,参数是它们的成员变量就好了。注意变的时候保持单一权责
- 注意职责的分隔。如果当你新增一个功能可能会影响其他时,说明耦合过强
好的代码示范
书的附录记录了好的代码示范
# 优秀的缩减了代码的长度,在简单代码的情况下,简洁是第一要义。所以允许在前面return
public boolean addCustomer(Customer customer)
{
if(this.customers.length == this.total) return false;
customers[total++] = customer;
return true;
}
重构
重构的过程
发生的时间:
- 当你想要给程序添加一个新特性的时候。
- 营地法则:保证你离开时的代码库一定比来时更健康。
第一步:
- 确保即将修改的代码拥有一组可靠的测试。
第二步:
- 从整个代码中分离出不同的关注点。
- 提炼代码到新的函数,关注又那些变量会离开原本的作用域
- 每走一步都需要测试一遍,保证不会出错
第三步:
- 提炼出新的函数后首先关注变量改名
- 关注函数的参数之间关系,是否真的需要传入
第四步:
- 去除临时变量,用内联函数进行替代。(我不太喜欢,因为内联函数名字比较长)
- 检查变量是否可以合并或去除。
第五步:
- 检查循环,将不同的任务拆分,可以允许多个循环,如果重构引入性能损耗,先完成重构,再做性能优化。
第六步:
- 拆分计算阶段与格式阶段,即输出的时候格式可能会经常需要变动,避免重复的代码,应定义一个对象的格式作为计算阶段与格式阶段的交互。
第七步:
- 多态取代条件表达式,如switch等
注意事项
- 为了简洁,在不会有太大变动的部分,可以采用一些不好的习惯。
重构的原则
- 重构是一个个小步骤,不是在迭代阶段的最后才执行的事情。
- 添加新功能时不应该修改已有代码,只管新功能,两顶帽子(重构和增加),每次只戴一顶
- 重构的时机:在添加新功能之前;当你需要思考这段代码到底在做什么;不需要重构的时机:不会有改动的地方,可以被API隐藏
- 面对遗留代码,不建议一股脑全部重构,更愿意随时重构相关代码,每碰到一块代码都保持营地法则。
什么情况下重构
结合重构的过程
- 奇怪命名与重复代码
- 过长的函数,当你感觉要注释的时候,就需要把这一部分提取到一个独立的函数中
- 全局变量,全局变量要么是常量要么就提供get/set方法。
- 可变数据(可以建立原则:如果更新一个数据结构,就返回一个全新的数据副本)
- 发散式变化 ,即同一个模块需要根据不同的原因发生变化,就要考虑是否可以拆分
- 数据泥团,许多参数都喜欢糅杂,所以要用类去为它瘦身(很常见,数据项会经常很多)
- 纯数据类常常以为这行为被放在了错误的地方,只要把处理数据的行为搬移到纯数据类里,就可以使情况大大改善。
构筑测试体系
编写优良的测试程序,可以极大提高自我的编程速度,即使不进行重构也一样如此。
测试法则
- 测试不是要把所有的错误找出来,所以你大可不必担忧繁杂的代码,而是让你更有自信的进行下一步。则保证核心主线功能正确
- 不是再每次迭代结尾时增加测试,而是只要写好一点功能就添加它们
- TDD测试驱动开发,为了添加这个功能,我需要写什么测试函数。
- 注意测试代码中那些重用的对象,有可能不同测试函数之间产生干扰导致结果出错。
- 每当你遇见一个bug,先写一个测试来清楚地复现它。