上一次的读书分享活动上面,已经有几位同事已经分享了《重构》这本书的读后感,以及他们对重构的一些认识。从他们的分享上面,我已经知道重构的重要性和什么是重构了。简单一点说,重构就是让糟糕的代码变成整洁的代码。糟糕的代码变成了整洁的代码,好处很明显,第一,自己看着舒服;第二,其他同事可以很快速的理解你的代码,很容易维护你写的代码。
既然重构这么好,为什么我们的系统很多代码并没有重构,旧的代码很烂,新加的代码还是很烂呢?
我认为,第一,我们缺乏对整洁代码的重视,包括公司层面,各个部门团队,以及个人,我来公司的时候,公司以及团队并没有跟我提这样的要求,说要写出整洁的代码,面对需求,只要功能实现,没有bug ,不会对你的代码质量有过多的要求,所以,我们都是快速实现功能,并不会去太多思考怎么写出整洁的代码,所以我们写出来的代码是一种“流水型”,也叫面向过程的一种代码,甚至很多时候,为了更快速的实现,经常会想到其他地方有类似功能实现的,就把代码拷贝过来,改一改,然后测试一下,觉得ok,没问题了,就交付出去了,以为没有bug 就已经做完了;第二,我们缺乏重构代码的技能,觉得花时间去重构对自己来说并不是一件容易的事情;第三,我想说的是,我们缺乏一种追求,对整洁代码的追求,对代码艺术的追求,我们对代码应该要有一定程度的洁癖。
用简单的语言总结一下,重构就是让代码整洁,代码不仅仅是写给机器读的,更重要的是要给人读的,整洁的代码其他人才能更容易,更轻松的读懂,理解,维护才更容易。什么时候重构呢?自己写的代码,写完以后,交付之前,必须要重构;维护代码,改代码的时候,你应该要重构;你去读别人代码,理解别人的代码的时候,应该重构;存在难以理解的代码的要重构。
带着从书里面学习到一些重构的技巧和方法的目的,我开始了本书的阅读。
第一章,通过一个实例开启了本书,用Java的多态进行了代码的重构。通过识别代码里面的坏味道,然后消除坏味道。
第二章,重构的原则,用一句话总结一下,目的只有一个,让代码整洁
第三章,代码的坏味道,讲了一些什么是代码的坏味道,书里面说了很多坏味道的类型,简单一点说吧,使代码看起来不整洁,不容易理解的都是坏味道
第四章,讲到了一个重构的前提,要有单元测试,有了单元测试,第一,可以提前发现代码中的问题;第二,进行重构的时候,心里可以更放心。最近,我也写了一些单元测试,有的是代码完成以后补上的,补单元测试的过程中,我对单元测试有这样一个理解,单元测试,就好像是记录自己读代码,理解代码的一个过程,比如说,代码中的一个if else逻辑块,补单元测试的时候,我就会写两个case,第一个,构造一个满足if条件的given ,第二个,构造一个满足else的given,这个过程就好像是从代码推导出需求的过程,跟TDD相反,这里也正体现了一个TDD的好处,从需求到Tasking,再到测试代码,再到代码实现,这样实现的代码就紧跟需求了,这里就先不展开了。
第五章,重构列表,感觉内容有点过时。里面提到了一个重构的基本技巧,小步前进,频繁测试,小步其实比大步前进速度会更快,因为小步不容易出错,每次的改动都基于上一次没有错误的基础上,步伐太大,出了问题之后,很可能要花更多的时间才能找出错误,这个我在最近的TDD和OO Bootcamp培训里面都有体会到这个小步的过程。但自己在工作中,还是做得不够好,没有养成一个及时跑测试的习惯,
第六章,重新组织函数
1,提炼函数,把过长的函数提炼出来,变成更小粒度的函数。这里我想分享一个我以前的一个相反的想法,很久以前,我去维护一个功能的时候,看代码,然后从代码的一个函数方法开始看,这个函数里面调用了很多其他的函数方法,函数方法里面有调用很多其他的函数方法,一直看下去,发现有很多层,我那个时候心里想,为什么不把这些代码都放在一个函数方法里面,我一口气就可以看完,那些代码其实就比较好的用了提炼函数这个方法,为什么我当时是那样想的呢?第一,我那个时候自己就是写一个很长的函数方法这样写代码的;第二,我没有采用快速理解代码的方式去读代码,通过函数名理解函数的方式,而是采取每行代码都要看的一种很笨的方式,所以,我要一层层的去找代码,会觉得很累,也记不住那么多代码逻辑;第三,我现在记不清那些具体的代码是什么,有可能,那些代码提炼出来的函数名字也取的不好,我看不出来他里面具体实现了什么,所以,提炼函数还有一个基本要做到的,就是要有很好的函数命名。
重构之前的代码,就好像我以前写的代码,而且是我以前比较赞同的一种写法,要我以前写,我觉得就应该写成这样子。这里我要突出讲的是里面的注释,以前,我很赞同加注释,我觉得,我在自己写的代码上面加注释,是对其他人会有帮助的,对比一下重构之后的函数,我发现,函数更加整洁,其实作者就是把重构前的注释信息当成提炼出来函数的名字了,我以前那种想法的出发点其实是挺好的,只是方式不对,这里重构之后的函数,其他人想读懂函数就可以直接看主函数,如果有必要知道更详细的信息才需要看里面具体调用函数的实现,这里我也想再次强调一点,函数方法名很重要,
2. 内联函数,和提炼函数可以说相反,去掉不必要的提炼
3.内联临时变量
4.以查询取代临时变量,减少临时变量,也有助于提炼函数,用现在的工具,都比较容易做到
5. 引入解释性变量,这个和以查询取代临时变量相反,为了代码更好理解,把表达式用临时变量解释,但一般不这么做,更好的做法是用提炼函数的方法,把表达式分别放在不同的函数方法里面
6. 分解临时变量,每个临时变量不要承担多个职责
7. 移除对参数的赋值,对参数赋值会很容易混淆,不容易理解代码,可以用临时变量来取代
以函数对象取代函数,过多的参数,导致很难提炼函数,所以把提炼出来的函数变成一个对象,所有的局部变量都可以变成函数对象的字段。
8. 以函数对象取代函数
9. 替换算法,用更好更整洁的代码替换旧的算法
第七章 在对象之间搬移特征
1,搬移函数
把函数放在更合适的地方,
2,搬移字段
跟搬移函数类似,把字段放在更合适的对象里面。
3,提炼类
每个类都是一个清楚的抽象,处理明确的责任,可以把大的类,分成小的类,每个类负责自己的事情。
4,将类内联化
和提炼类相反。
5,隐藏“委托关系”
利用Java的封装特征,隐藏对象中的对象。
6,移出中间人
和隐藏委托关系相反。
7,引入外加函数
这个有点像提炼函数。
8,引入本地扩展
调用的类,不能满足自己需求,又不能改动,可以创建一个新类继承,然后再加自己的需要的功能。
总结一下吧,这一章节,讲了很多详细的方法说明怎么去重构,可以注意到里面有很多方法是俩俩对应完全相反的。所以,这些重构的手法并不是生搬硬套的公式,重构的时候应该灵活运用,需要在工作中慢慢积累经验。
第八章,重新组织数据
1,自封装字段
Java的特征之一,这个也没有一定的标准,可以结合实际情况运用,这里提到一个比较明显的好处,当类扩展的时候,很容易对函数覆写。
2,以对象取代数据值
将数据项变成对象
3,将值对象改为引用对象
在第二节中,再增加一个,在多处值对象中,引用同一个对象
4,将引用对象改为值对象
和上一节相反。
5,以对象取代数组
这个很容易理解,特别是数组中的值含义不同的时候,用对象来替换,可以跟清晰的表达各个字段的含义。
6,复制“被监视的数据”
建立观察者模式,将数据复制到一个领域对象中。
7,将单向关联改成双向关联
8,将双向关联改成单向关联
9,以字面常量取代魔法数
代码中的魔法数,hardcode 都应该用常量取代。
10,封装字段
不直接暴露对象中的属性,Java的封装特征。
11,封装集合
12,以数据类取代记录
项目中经常见到的DTO VO类
13,以类取代类型码
以新的类替换还数值类型码,让代码更容易理解。
14,以子类取代类型码
15,以State Strategy 取代类型码
用策略模式取代类型码
16,以字段取代子类
去掉不必要的子类。
这一章同样是讲了很多具体的重构手法,还是那句话,需要慢慢在实际项目中多练习,才能熟练掌握。
第九章,简化条件表达式
1,分解条件表达式。
把表达式提炼成函数,并取一个适当的命名,可以让代码整洁易懂。
2,合并条件表达式
很多分开的表达式可以合并在一起,然后再把合并的表达式提炼成函数。每个过程都可以采用小步前进,频繁测试的方式进行一步步重构。
3,合并重复的条件表达式
很多条件里面都有相同的代码,跟表达式的结果没有关系,可以放在表达式外面去做。
4,移除控制标记
用break return来取代一些临时的控制标记。
5,以卫语句取代嵌套条件表达式
通过各种方式,减少嵌套层级。
6,以多态取代条件表达式
这个也是比较常见的,利用java的多态特征来处理不同的条件的处理
7,引入Null对象
我并不是很理解这里,感觉不是很有必要,有其他更好的方式可以替换,特别是在Java8里面。
8,引入断言
增加断言,增强代码可读性
第十章,简化函数调用
1. Rename Method
要想成为一个真正的编程高手,起名的水平是至关重要的。
用现在的工具很容易修改函数的名称。
2. Add Parameter
3. Remove Parameter
4. Separete Query from Modifier
rule: 任何有返回值的函数,都不应该有看得到的副作用
5. Parameterize Method 令函数携带参数
用函数携带参数,去掉重复代码
6. 以明确函数取代参数
去除if switch 判断,用明确函数取代参数
7. 保持对象完整
不需要从对象中取多个值作为参数传递,直接传递整个对象。
8. 以函数取代参数
有些参数从一个函数中取得,然后又传递给另外一个函数,可以在接受参数的这个函数里面直接用函数的获取,减少函数参数的传递。
9. 引入参数对象
出现多个可以放在对象中的参数,把参数提炼成函数一个函数,然后调用这个函数的时候传递对象,减少函数调用参数的个数。
10. 移除设置函数
这个不是很理解,在实际项目中,对象中的值经常要改变,我们基本都是通过set方法去改变对象中属性的值。
11. 隐藏函数
尽可能降低所有函数的可见度,易于维护。
12. 以工厂函数取代构造函数
13. 封装向下转型
14. 以异常取代错误码
坚信:代码的可理解性应该是我们虔诚追求的目标
15. 以测试取代异常
个人不太喜欢抛异常这种做法。
第十一章 处理概况关系
类的概括关系即继承关系,主要是将函数上下移动于继承体系之中
1. Pull Up Field
两个子类拥有相同的字段,将该字段移动至超类。
2. Pull Up Method
函数在各个子类中产生完全相同的结果,移动到超类
3. Pull Up Constructor Body
把子类构造函数相同的部分移动到超类,在子类构造函数里面调用super
4. Push Down Method
超类中某个函数只和部分子类有关,移动到需要的子类中
5. Push Down Field
超类中的某个字段,只有部分子类用到,移动到子类中
6. Extract Subclass
将类中只被某些实例用到的特性移动到子类中
7. Extract Superclass
为两个具有相似特性的类建立一个超类
8. Extract Interface
若干个类具有相同的接口,把相同的接口的子集提炼到独立接口中。
9. Collapse Hierarchy 折叠继承体系
超类和子类区别不大,应合为一体。
10. Form Template Method 塑造模板函数
有一些子类,相应的某些函数以相同的顺序执行类似的操作,每个操作细节不同,将这些操作分别放在独立的函数中,使用相同的方法名,然后在父类中抽象出那些方法。 通过继承,消除重复。
11. Replace Inheritance with Delegation 已委托取代继承
子类只使用超类接口中的一部分,在子类中新建一个属性字段保存超类,调整子类函数,改为委托超类,去掉继承关系。
12. Replace Delegation with Inheritance 以继承取代委托
和上一节正好相反。
第十二章 大型重构
根据需要安排自己的工作,只在需要添加新功能或者修补错误时才进行重构。不必一开始就完成整个系统的重构,重构程度只要能满足其他任务的需要就行了。应该要有持续而无处不在的重构。
1. Tease Apart Inheritance 梳理并分解继承体系
2. 将过程化设计转化为对象设计
3. 将领域和表述/显示分离
4. 提炼继承体系
第十三章 重构,复用与现实
为什么不肯重构你的程序?
1. 你不知道如何重构
2. 如果这些利益是长远的,何必现在付出这些努力?长远看来,说不定当项目收获这些利益是,自己已经不在职位上了
3. 代码重构是一项额外工作,老板付钱给你,主要是让你编写新功能
4. 重构可能破坏现有程序
第十四章 重构工具
Intellij IDEA
第十五章 总结
没有最好,只有更好。重构应该是永无止境的,只有持续不断的重构,才能让代码越来越整洁。通过阅读本书,首先,自己已经觉得重构是必须的,必要的。第二点,关于具体的重构手法,有些是用过的,有些是刚接触的,需要在实际运用中持续不断的学习和使用。