重构的原则
重构的定义:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。也可以说:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
定义的扩展:第一,重构的目的是使软件更容易被理解和修改。第二,重构不会改变软件可观察的行为。
是用重构技术时,需要把时间分配给两种截然不同的行为:添加新功能,以及重构。添加新功能时,不应该修改既有代码,只管添加新功能。重构时不能再添加功能,只管改进程序结构。
重构的目的:
改进软件设计
使软件更容易理解
帮助找到BUG
提高编程速度
重构的时机
三次法则
添加功能时
修补错误时
复审代码时
重构的难题
数据库
修改接口
难以通过重构手法完成的设计的改动
何时不该重构
重构方案实施
重新组织函数
* Extract Method(提炼函数)
函数过长,可以根据语义分解函数
需要注释才能理解的代码放到独立函数中
* Inline Method(内联函数)
针对某些函数,其内部代码和函数名称同样清晰易读,这样的函数应该去掉
当手上有一群组织不合理的函数时,可以先将它们都内联到一个大型函数中,然后再提炼
* Inline Temp(内联临时变量)
当某个临时变量被赋予了某个函数调用的返回值,可以直接去掉临时变量
* Replace Temp with Query(以查询取代临时变量)
局部变量会使代码难以被提炼,应该尽可能把它们替换为查询式(用一个简单的查询函数替换)
*Introduce Explaining Variable(引入解释性变量)
在条件逻辑中,表达式复杂难以阅读,可以使用临时变量帮助解释
在较长的算法中,可以使用临时变量来解释每一步运算的意义
* Split Temporary Variable(分解临时变量)
如果临时变量承担多个职责,就应该被替换为多个临时变量,每一临时变量只承担一个职责
* Remove Assignments to Parameters(移除对参数的赋值)
不要对参数赋值,应该是临时变量替代.
* Replace Method with Method Object(以函数对象取代函数)
如果一个函数中局部变量泛滥成灾,可以提炼函数对象,将所有局部变量都变成函数对象的字段.
* Substitute Algorithm(替换算法)
如果一件事可以有更清晰的方式,就应该以较清晰的方式取代复杂的方式.
在对象之间搬移特性
* Move Method 搬移函数
如果一个类有太多行为
一个类与另一个类有太多合作而形成的高度耦合
* Move Field 搬移字段
如果对于一个字段,在其所驻类之外的另一个类中有更多函数使用了它,那么就要搬移这个字段了
* Extract Class 提炼类 : 建立一个新类,将相关的字段和函数从旧类搬移到新类
当类含有大量函数和数据时,要考虑分离出去一部分.
* Inline Class 将类内联化
当类不再承担足够责任,不再有单独存在的理由时,可以考虑把类塞进另一个类中
* Hide Delegate 隐藏“委托关系”
如果某个客户先通过服务对象的字段得到另一个对象,然后调用后者的关系,这样就暴露了委托关系,我们可以在服务对象上放置一个简单的委托函数,将委托关系隐藏起来.
* Remove Middle Man 移除中间人
在委托调用中,随着受托类的特性的增加,服务类完全变成了一个"中间人",此时应该让客户直接调用受托类.
* Introduce Foreign Method 引入外加函数
如果你的类无法提供一项新的功能,你可以自行添加一个新函数
* Introduce Local Extension 引入本地扩展
当需要的外加函数比较多时,可以将这些函数组织起来,放到一个恰当的地方(子类化和包装可以实现)
重新组织数据
* Self Encapsulate Field 自封装字段
如果你想在访问超类中的一个字段,却又想在子类中对这个变量的访问改为一个计算后的值,此时可以使用此手法
* Replace Data Value with Object 以对象取代数据值
当用数据项表示简单情况时,当数据项的值开始产生不好的代码感观时,就可以使用对象代替了
* Change Value to Reference 将值对象改为引用对象
有时候,你会从一个简单的对象开始,在其中保存少量不可修改的数据.而后,你可能会希望给这个对象加入一些可修改的数据,并确保对任何一个对象的修改都能影响到所有引用此一对象的地方,这时就可以把这个对象变成一个引用对象了
* Change Reference to Value 将引用对象改为值对象
如果引用对象开始变得难以使用,也许就应该将它改为值对象
* Replace Array with Object 以对象取代数组
当数组中容纳多种不同的对象时,请把数组改为对象
* Duplicate Observed Data 复制“被监视数据”
将用户界面和处理业务的逻辑代码分离时,数据往往需要内嵌到GUI控件中,因此需要保留两份,并保持同步
* Change Unidirectional Association to Bidirectional 将单向关联改为双向关联
开发初期,你可能会在两个类之间建立一条单向连接,是其中一个类可以引用另一个类。随着时间的推移,你可能发现被引用类需要得到其引用者以便进行某些处理,这个时候可以执行此法
* Change Bidirectional Association to Unidirectional 将双向关联改为单向关联
当双向关联不需要时,就应该去掉双向关联
* Replace Magic Number with Symbolic Constant 以字面常量取代魔法数
应该把Magic Number 替换为字面常量
* Encapsulate Field 封装字段
取消Public数据,以实现数据隐藏
* Encapsulate Collection 封装集合
取值函数不应该返回集合,设置函数也不应该直接设置集合
* Replace Record with Data Class 以数据类取代记录
当你面对一个遗留程序,后者需要通过传统API与记录结构交流,或者处理从数据库读取的记录,这时候,需要创建一个接口类,用以处理这些外来数据
* Replace Type Code with Class 以类取代类型码
以一个新的类取代数值型类型码
* Replace Type Code with Subclasses 以子类取代类型码
当类型码影响宿主类的行为时,可以借助多态来处理,使用子类取代类型码
* Replace Type Code with State/Strategy 以State/Strategy 取代类型码
你又一个类型码,它会影响类的行为,但是无法通过继承手法消除时,可以以状态对象处理类型码
* Replace Subclass with Fields 以字段取代子类
你的各个子类唯一的差别只是“返回常量数据”的函数,那么可以使用字段取代子类
简化条件表达式
* Decompose Conditional 分解条件表达式
当你有一个复杂的条件语句时,可以分别从if, then, else三个段落中提炼独立的函数
* Consolidate Conditional Expression 合并条件表达式
当有一系列条件测试,都得到相同的结果时,将这些测试条件合并为一个条件表达式,并提炼为一个独立的函数
* Consolidate Duplicate Conditional Fragments 合并重复的条件片断
在条件表达式的每个分支上有着相同的一段代码,应将这段重复代码搬移到条件表达式之外
* Remove Control Flag 移除控制标记
在一系列布尔表达式中,某个变量带有"控制标记"的作用,应以break语句或者return语句取代控制标记
* Replace Nested Conditional with Guard Clauses 以卫语句取代嵌套条件表达式
函数中的条件逻辑使人难以看清正常的执行路径,应该使用卫语句表现所有特殊情况
* Replace Conditional with Polymorphism 以多态取代条件表达式
当有条件表达式是根据对象类型的不同而选择不同的行为时,可以将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数
* Introduce Null Object 引入Null 对象
当有需要再三检查某对象是否为null时,应该将null值替换为null对象
* Introduce Assertion 引入断言
当某一段代码需要对程序状态做出某种假设,应该以断言明确表现这种假设
简化函数调用
* Rename Method 函数改名
函数名称未能揭示函数的用途时,应该修改函数名称
* Add Parameter 添加参数
某个函数需要从调用端得到更多信息,可以为此函数添加一个对象参数,让该对象带进函数所需信息
* Remove Parameter 移除参数
当函数本体不再需要某个参数时,应将该参数去除
* Separate Query from Modifier 将查询函数和修改函数分离
某个函数既返回对象状态,又修改对象状态时,应建立两个不同的函数,其中一个负责查询,另一个负责修改
* Parameterize Method 令函数携带参数
若干函数做了类似的工作,但在函数本体中却包含了不同的值,这个时候应建立单一函数,以参数表达那些不同的值
* Replace Parameter with Explicit Methods 以明确函数取代参数
当有一个函数完全取决于参数值而采用不同行为,可以针对该参数的每一个可能值,建立一个独立函数
* Preserve Whole Object 保持对象完整
如果你从某个对象中取出若干值,将它们作为某一次函数调用时的参数,就要改为转递整个对象
* Replace Parameter with Methods 以函数取代参数
对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数。这个时候,可以让接受者去除该项参数,并直接调用前一个函数
* Introduce Parameter Object 引入参数对象
某些参数总是很自然地同时出现,那么请以一个对象取代这些参数
* Remove Setting Method 移除设值函数
类中的某个字段应该在对象创建时被设值,然后就不再改变。那么就要去掉该字段的所有设值函数
* Hide Method 隐藏函数
当函数从来没有被其他任何类用到,那么就要将这个函数修改为private
* Replace Constructor with Factory Method 以工厂函数取代构造函数
当你希望在创建对象时不仅仅是做简单的构建动作时,可以将构造函数替换为工厂函数
* Encapsulate Downcast 封装向下转型
某个函数返回的对象,需要由函数调用者执行向下转型,这个时候应该将向下转型的动作移到函数中
* Replace Error Code with Exception 以异常取代错误码
某个函数返回一个特定的代码,用以表示某种错误情况,应该改用异常
* Replace Exception with Test 以测试取代异常
面对一个调用者可以预先检查的条件,你却抛出了一个异常,可以修改调用者,使它在调用函数之前先做检查
处理概括关系
* Pull Up Field 字段上移
当两个字类拥有相同的字段时,将该字段移动至超类
* Pull Up Method 函数上移
有些函数,在各个子类中产生完全相同的结构,应该将该函数移至超类
* Pull Up Constructor Body 构造函数本体上移
各个子类中拥有一些构造函数,它们的本体几乎完全一致,那么可以把它们移动至超类
* Push Down Method 函数下移
超类中的某个函数只与部分子类有关,将这个函数移至相关的那些子类中去
* Push Down Field 字段下移
超类中的某个字段只被部分子类用到,将这个字段移动到需要它的那些子类中去
* Extract Subclass 提炼子类
类中的某些特性只被某些实例用到,可以新建一个字类,将那些特性移到子类中
* Extract Superclass 提炼超类
两个类有相似的特性,可以为这两个类建立一个超类,并将相同特性移至超类
* Extract Interface 提炼接口
若干客户使用类接口中的同一个子集,或者两个类的接口有部分相同,可以将相同的子集提炼到一个独立函数中
* Collapse Hierarchy 折叠继承体系
超类和子类之间无太大差别,可以将它们合为一体
* From Template Method 塑造模板函数
一些子类,其中相应的某些函数以相同顺序执行类似操作,但各个操作在细节上有所不同,这个时候,可以将这些操作分别放进独立函数,并保持它们有相同的签名,于是原函数也就变得相同了,然后将原函数上移至超类
* Replace Inheritance with Delegation 以委托取代继承
某个子类只使用超类接口中的一部分,或者根本不需要继承而来的数据,那么可以用委托取代继承
* Replace Delegation with Inheritance 以继承取代委托
如果两个类之间使用了委托关系,并经常为整个接口编写许多极简单的委托函数,那么让委托类继承接受委托类
大型重构
Tease Apart Inheritance 梳理并分解继承体系
建立两个继承体系,并通过委托关系让其中一个可以调用另一个
Convert Procedural Design to Object 将过程化设计转化为对象设计
将数据记录变成对象,将大块的行为分成小块,并将行为移入相关对象之中。
Separate Domain from Presentation 将领域和表述/显示分离
将领域逻辑分离出来,为它们建立独立的领域类
Extract Hierarchy 提炼继承体系
建立继承体系,以一个子类表示一种特殊情况