2008-11-21 version 1.1
不改变代码外在行为的前提下,对代码进行修改,用以改善程序的内部结构。
1) 小步前进,每次更改一点点,在发生错误时能进行回归;大量的更改叫做重写;
2) 重构后测试失败一定要进行回归,而不是就地调试;
3) 勤于测试
a) 在代码更改前添加欠缺的测试
b) 测试的实用主义原则:放过那些肯定不会出错的函数,如get、set函数;
4) 抵制诱惑:重构不能改变代码外在表现,不要试着去改变原代码中的错误;将外在行为的更改与重构分开!——Kent Beck
5) 重构时尽量不要添加、更改测试;
重构是一种早期广泛应用于smalltalk的方法,在OO发展的早期(80年代),重构技术便已经开始产生并发展;关于重构的第一份正式文本,是一篇博士论文;Martin Fowler《refactoring》一书中文版于2003年翻译出版;
Refactoring Browser是一个用于smalltalk的重构工具;VAssistX也有一些重构功能;
l 更改接口:首先让旧接口调用新接口,通过再删除新接口;
l 重构与性能:更多的封装、委托,看起来设计优良的系统跑起来慢;但大量的经验表明,必须经过测试才能确定热点,任何分析、臆断都有可能是错的;“重构时不要过多考虑性能,优化时再考虑;”
小类与小函数,避免巨型类与巨型函数;
责任明确的类;
首先说明:并非有任何一些臭味的代码都是不好的代码,如某些设计模式就可能有过多的依赖的问题;对此问题的原则是:隔离变化,将某个因素可能引发的变更尽量放到一起;
解决办法:使用Extract Method;出现在不同类中的重复代码,使用Extract Class将其独立到一个类中;
“如果需要用注释来说明一段代码,那就把这段代码提炼到一个函数中,同时起一个足够说明问题的名字。”
Extract Method分解函数;
类拥有过多数据成员和/或成员函数;
Replace parameter with method、introduce parameter object等方法;
某个class经常因为不同的原因在不同的方向上变化;
理想上每个类应该只因为一个原因而变化;
Extract class将一个方向上的变化提炼出来;
遇到一个变化需要在多个class做小修改;
Move method与Move Field将需要修改的因素集中到一个class中;
Class的某个函数过于依赖其他一个class;
Move Method将其移到那个class中,或先Extract method将依赖部分提取出,再move新函数;
总是一起出现的数据,如几个相同参数出现在一系列函数中,或好几个class中;
Extract class将它们放到一个独立对象中;
程序员不愿使用小class,而偏向使用基本数据类型;
OO程序的一个“最明显”特点:少用switch语句;多态是一个替换方案;
减少switch的方法:
Extract method先将switch语句提出;再move method将它移到需要多态性的类中;使用replace type code with subclass或replace type code with state/strategy,实现类层次;replace conditional with polymorphism替换条件判断;
一个简单的方法:replace parameter with explicit methods;
平行继承体系,每当为一个class增加一个subclass,就需要为另一个class相应增加一个subclass;
解决之道:让一个继承体系refer to另一个继承体系,再使用move method/move field解决之;
几乎无用的class;
对未来变化过分的预测——“总有一天会用到”;
某些数据成员仅为特定场景、method而设置;
Extract class将该数据成员及相关函数提炼出来,这可能会是一个method object;
对象相互委托,需要通过一个调用链(message chain)才能获得最终的数据;如a.getB().getC().getD().getE();
解决之道:
1,如上例更改成a.getB().getE(),但此时B可能会变成一个Middle man;
2,更好的处理,将E的使用提取出来,再推入chain中;
Class过多接口都委托给其他class完成;
Remove middle man直接与实责对象交互,或replace delegation with inheritance把middle man变成实责class的subclass;
两个class过于密切,相互需要知道对方的大量private信息;
Move method、move field、extract class等;
两个函数、类使用不同的定义但做了同样的事;
类库不够用;
Introduce foreign method、introduce local extension;
仅拥有数据成员及get、set这些成员的函数的class;
注意类是数据及相关操作的集合;
将get、set函数的用户的其他某些行为尽量移植到data class中;
Subclass不需要superclass的所有数据和函数;
如果不需要继承接口,尝试replace inheritance with delegation;
或将子类不需要的部分提炼成一个子类的兄弟类;
在comments被当成除臭剂时,重构代码;
l 将一段代码提取出来作为一个新的函数;
l 重点考虑临时变量、新函数的参数与返回值;
l 将函数调用替换成函数体;
l 确保函数没有多态性;
l 使用表达式替换临时变量;
l 将临时变量的计算式提炼成函数(即query),再用它替换掉临时变量;
l 用于只被赋值一次的临时变量;如果赋值超过一次,考虑split temporary variable;
l 将变量声明为const、final,编译;再进行提取、测试;
l 将复杂表达式的结果保存到一个const temp中,以temp名解释表达式的含义;
l Temp被赋值超过一次,此时为每次赋值都创造一个独立的temp;
l 如果函数一个参数被赋值,则使用一个temp取代该参数;
l 参数只用于表示“被传递进来的东西”,而不被同时作为函数的一个temp,能提高代码清晰度;
l 使用单独对象替换大型函数;
l 该方法比较复杂,To be continued;
l 将函数体替换成一个更清晰的算法;
l Class的一个函数与另一个class过于密切;
l 考虑该函数用到的其他成员是否也应该被移动;
l 检查函数是否没有多态性;
l 首先尝试将原函数改成delegation,编译测试,再考虑删除原函数;
l 首先将public field隐藏,编译测试;再执行移动;
l 从原有的class中分离出一个新的class;
l 小步操作,多次测试;
l 删除职责太少的class;
l 首先在目标类中实现要删除的class的public接口;改成委托,测试;最后执行删除;
l Client直接调用了server的委托类,将其改成间接调用;
l 某个class做了过多的简单委托动作,此时让client直接调用delegate;
l 一个server class不能提供满足需要的函数,此时在client class中定义一个需要的函数;
l 该函数不应使用client class的任何特性;server class的object应该是函数的第一个参数;
l Server class不能提供需要的功能,此时引入一个新的class,该class是server class的subclass或wrapper;
l 为field建立set/get函数;
l 暂时将field改名,编译查出所有的引用点;
l 数据项需要别的数据和行为,此时建立一个新的class表达该数据项及其相关部分;
l Reference object,代表真实世界中的实物;value object,完全使用其包含的数据值来定义,如日期;
l To be continued
l Reference对象小而不可变时,变成value对象以简化管理;
l 使用一个哑数据class代替record,
l 这个class当前只有get、set函数,进一步的重构这种情况会改变的;
l 一个函数需要用到object的数个成员,那么就使用该object作为函数参数,而不是独立的那几个成员;
l Exception用于将错误错误从一般处理中分离出来;这是一种集中处理错误的方式;
l 还是没有告诉我们为什么要使用exception!
l Exception仅应被用于处理那些意料外的错误,而不是“条件检查”的替代品;
l 使用正常的条件检查来避免异常处理;
分析类体系承担的责任,将不同的责任拆分到不同的类结构中,如使用bridge模式在不同的类体系中建立关联;
将record转化成带数据及其存取的class;将过程化代码提取到某个class中;分解大函数;
将业务逻辑从展现中拆分出来;