用户您好!请先登录!

《重构》概要

《重构》概要

1. 重构概述

1.1 重构的概念(What)

Refactoring

名词:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低修改成本。

动词:使用一系列重构方法,在不改变软件可观察行为的前提下,调整其结构。

1.2 为什么要重构(Why)

改进软件设计

提高代码质量和可读性,使软件系统更易理解和维护

帮助尽早的发现缺陷

提高编程速度

1.3 何时重构(When)

何时重构:

1)随时随地进行。

2)三次法则:第一次做某件事只管去做;第二次做类似的事会产生反感,但无论如何还是可以去做;第三次 再做类似的事情,就应该重构了。

3)添加功能

4)修复bug

5)复审代码,即Code Review时候

何时不该重构:

1)代码是在太混乱了,设计完全错误

2)如果项目已近最后期限,应该避免重构

3)重构还不如重新编码

1.4 重构流程

1)读懂代码(包括测试代码)

2)进行重构

3)运行所有的单元测试

1.5 重构与设计

重构与设计是互补的,程序应该先设计,但在开始编码后,设计上的不足可以用重构来弥补。设计应该是适度的设计,而不必过度设计。如果能很容易的通过重构来适应需求的变化,那么就不必过度的设计,当需求改变时再重构代码。

1.6 重构要点

1) 如果你发现自己需要为程序添加一个功能,而代码的结构使你无法很方便地达到目的,那就先重构那个程序,使功能的添加比较容易进行,然后再添加那个功能。

2) 重构前,先检查自己是否有一个可以依靠的测试机制。这些测试必须有自我检验的能力。

3) 重构技术就是以微小的步伐修改程序。如果你犯下错误,很容易便可发现它。

4) 任何一个傻瓜都能写出计算机可以理解的代码。唯有写出让人能够容易理解的代码才是真正优秀的程序员。

5) 事不过三,三则重构。

6) 不要过早发布接口。谨慎修改你代码的各种规范,使重构更顺畅。

7) 当你感觉需要撰写注释时,先尝试重构,试着让注释显得多余。

8) 确保所有测试都完全自动化,让它们检查自己的测试结果。

9) 一套测试就是一个强大的bug检测器,能够大大缩减查找bug所需要的时间。

10) 频繁地进行测试。每次编译请把测试也考虑进去——每天至少执行每个测试一次。

11) 每当你收到bug报告,请先写一个单元测试来暴露这这个bug。

12) 编写未臻完善的测试并实际运行,好过对完美测试的无尽等待。

13) 考虑可能出错的边界条件,把测试火力集中在那儿。

14) 当事情被大家认为应该会出错时,别忘了检查是否抛出了预期的异常。

15) 不要因为测试无法捕获所有bug不就写测试,因为测试的确可以捕捉到到大多数bug。

2. 代码的坏味道

1. Duplicated Code:重复代码

指不同的地方出现相同的程序结构。

重构方法:Extract Method、Extract Class、Pull Up Method、Form Template Method

2. Long Method:过长函数

重构方法:Extract Method、Replace Temp with Query、Replace Method with Method Object、Decompose Conditional

3. Large Class:过大的类

重构方法:Extract Class、Extract Subclass、Extract Interface、Replace Data Value with Object

4. Long Parameter List:过长参数列表

重构方法:Replace Parameter with Method、Introduce Parameter Object、Preserve Whole Object

5. Divergent Change:发散式变化

一个类经常由于不同的原因向不同的方向变化。实际上是违法了单一职责原则。

重构方法:Extract Class

6. Shotgun Surgery:霰弹式修改

一种变化会导致好几个类的修改。

重构方法:Move Method、Move Field、Inline Class

7. Feature Envy:依恋情结

类中的方法对另一个类的访问超过了对所在类的访问。

重构方法:Move Method、Move Field、Extract Method

8. Data Clumps:数据泥团

指喜欢经常成群出现的多个数据项,如两个类中相同的字段,或者许多方法中相同的参数签名。

重构方法:Extract Class、Introduce Parameter Object、Preserve Whole Object

9. Primitive Obsession:基本类型偏执

热衷于使用int,long,String等基本类型而不是有意义的小对象。

重构方法:Replace Parameter with Method、Extract Class、Introduce Parameter Object、Replace Array with Object、Replace Type Code with Class、Replace Type Code with Subclass、Replace Type Code with State/Strategy

10. Switch Statement:Switch语句

相同的Switch语句散落在各处,需要添加一个case时需要修改多个地方。

重构方法:Replace Conditional With Polymorphic、Replace Type Code with Subclass、Replace Type Code with State/Strategy、Replace Parameter with Explicit Methods、Introduce Null Object

11. Parallel Inheritance Hierarchies:平行继承体系

如果一个类增加一个子类,另一个类也必须相应的增加一个子类。

重构方法:Move Method、Move Field

12. Lazy Class:冗余类

无用的类

重构方法:Inline Class、Collapse Hierarchy

13. Speculative Generality:夸夸其谈未来性能

指无用的抽象类,无用的预留参数等当前没有用到的部件。这个往往是过度设计的结果。

重构方法:Collapse Hierarchy、Inline Class、Remove Parameter、Rename Method

14. Temporary Field:令人迷惑的临时字段

指仅在特定环境下使用的实例变量,比如为了方便某个方法调用而在对象中添加的字段,该字段仅在这个方法中有用。

重构方法:Extract Class、Introduce Null Object

15. Message Chains:过度耦合的消息链

指客户向一个对象索取另一个对象,然后在向后者索求另一个对象,然后在索求另一个对象——客户与查找过程的紧密耦合。

重构方法:Hide Delegate

16. Middle Man:中间人

过度委托,一个类中的大部分方法都委托给其他类。

重构方法:Remove Middle Man、Inline Method、Replace Delegation with Inheritance

17. Inappropriate Intimacy:狎昵关系

两个类大量探究彼此的private部分。

重构方法:Move Method、Move Field、Change Bindirectional Association to Unidirectional、Replace Inheritance with Delegation、Hide Delegate

18. Alternative Classes with Different Interface:异曲同工的类

类名不同但功能相似。

重构方法:Rename Method、Move Method

19. Incomplete Library Class:不完善的类库

指现有的功能不能满足要求。

重构方法:Introduce Foreign Method、Introduce Local Extension

20. Data Class:纯稚的数据类

指只有公共成员变量,或只有字段和get/set方法的类。

重构方法:Move Method、Encapsulate Field、Encapsulate Collection

21. Refused Bequest:被拒绝的遗赠

指子类只使用了父类的部分方法。

重构方法:Replace Inheritance with Delegation

22. Comments:过多的注释

过多的垃圾注释,或者代码太复杂,必须使用注释说明。

重构方法:Extract Method、Introduce Assertion

3. 重新组织函数

Extract Method:抽取方法

将一段独立的代码抽取到一个新的方法,并以它“做什么”来命名。

无局部变量:直接抽取;

局部变量在抽取的方法中只读:以参数传递;

局部变量在抽取的方法中被赋值:

如果变量只在抽取的代码中使用:将变量的声明也抽取到方法中

抽取的代码之外也使用了这个变量:

抽取的方法之后没有使用:在抽取的方法中直接修改

抽取的方法之后有使用:返回值返回

如果需要返回的代码超过一个,则暂不抽取

Inline Method:内联方法

如果一个方法的实现很简单,并且实现本身和方法的名称一样通俗易懂,则直接在方法的调用点用方法的实现代替方法调用。如果方法被子类覆写,或者涉及递归等复杂的情况,则暂不重构。

Inline Temp:内联临时变量

如果一个临时变量只被一个简单的表达式赋值,而且这个临时变量妨碍了其他重构手法,则将对这个临时变量的引用替换为这个赋值表达式。使用该方法要确保赋值表达式没有副作用,而且保证变量后面没有被赋值(可以通过将变量声明为final测试)。

Replace Temp With Query:查询取代临时变量

如果一个临时变量保存某一个(组)表达式的计算结果,则将表达式提炼为一个方法,这个新方法也可以被其他方法调用。

Introduce Explaining Variable:引入解释性变量

将复杂表达式(或其中一部分的)结果保存到一个临时变量,并以它的用途命名该临时变量,提高可读性。

Split Temporary Variable:分解临时变量

如果一个临时变量被赋值超过一次,而且它不是循环变量,也不用于收集计算结果,则针对每次赋值创造一个新的临时变量,提高可读性,避免混淆。

Remove Assignments to Parameters:移除对参数的赋值

不要对方法传递进来的参数赋值,尤其是混用值传递和引用传递的时候,会降低代码清晰度,应该建一个临时变量,并将参数传递给它,如果确实需要修改传递进来的值,请使用返回值。

Replace Method with Method Object:以方法对象取代方法

如果代码使用了很多临时变量,无法进行方法抽取,则这个方法变成一个新的类,那么方法的临时变量就会变成新类的实例域,然后就可以在该类中将这个大方法分解成多个小方法,而旧的方法调用者改成新建一个类对象,并调用相应的方法。

Substitute Algorithm:替换算法

如果两个算法做从相同的事情,则保留清晰的那个算法。

4. 在对象之间迁移特性

Move Method:迁移方法

如果一个类中某个方法跟另一个类的交互更多,则这个方法可能放错了地方,考虑迁移到那个类。

Move Field:迁移字段

如果一个类中的某个字段更多的被另一个类使用,则这个字段可能放错了地方,考虑迁移到那个类。

Extract Class:抽取类

某个类做了两个类应该做的事情,比如某些字段经常一起变化,某些字段和某些方法总是一起出现,则考虑将它们提取出来,形成一个新的类。

Inline Class:内联类

如果一个类没有做太多的事情,则将考虑将其特性迁移到另一个类,并将其删除。

Hide Delegate:隐藏委托

如果一个类A经常需要通过其委托类B来调用另一个类C,则在委托类B中建立委托方法,类A通过调用B的委托方法访问类C。

Remove Middle Man:移除中间人

如果一个类做了太多简单的委托操作,则删除这个类,让客户端直接调用受托类。

Introduce Foreign Method:引入外加函数

当需要为类新加一个方法,但无法修改该类时,可以新建一个方法,并将该类的对象作为参数传递进去,在这个方法中完成新的功能。

Introduce Local Extension:引入本地扩展

当需要为类新加一个方法,但无法修改该类时,可以让一个新类继承这个类,或者成为这个类的包装类,并在这个新类中实现新的功能。

5. 简化方法调用

Rename Method:重命名方法

如果方法名称未能正确表明方法的用途,则重命名方法

Add Parameter:添加参数

如果方法需要从调用端得到更多的信息,则添加参数

Remove Parameter:删除参数

如果方法不再需要某个参数,则删除它

Separate Query From Modify:分离查询和修改的方法

如果一个方法中既查询了一个对象,也修改了一个对象,则建立两个方法,一个负责查询,一个负责修改。

Parameterize Method:让方法携带参数

如果几个方法做了类似的操作,只是数据不同,则将这几个方法合并为一个方法,并用参数表达那些不同的值。

Replace Parameter with Explicit Method:用明确的方法替换参数

如果有一个方法,完全根据不同的参数值采用了不同的行为,则将这个方法拆分,为参数的每一个可能的值建立一个方法。

Preserve Whole Object:保持对象完整

如果有个方法的参数全部来自于一个对象,则改为使用这个对象作为参数。

Replace Parameter with Method:已放到取代参数

对象调用某个方法,并将其作为另一个方法的参数,如果另一个方法也能调用这个方法,则去除参数,直接让其调用这个方法。

Introduce Parameter Object:引入参数对象

如果某些参数总是一起出现,则使用一个类对象来代替这些参数,可以减少参数数量。

Remove Setting Method:删除设值方法

如果类中的某字段应该在创建时设置,此后不再变化,则删除set方法。

Hide Method:隐藏方法

如果一个方法从来没有被其他方法使用,则改为private

Replace Constructor with Factory Method:使用工厂方法取代构造器

如果创建对象不是仅仅做简单的对象构建,如根据不同的参数进行不同的构建,或者需要控制对象的实例个数,则使用工厂方法取代构造器

Encapsulate Downcast:封装向下转型

如果某个方法返回的对象需要调用者执行向下转型,则将转型动作移动到方法中。

Replace Error Code with Exception:使用异常代替错误码

对于返回特定的值(如-1)表示错误的方法,考虑改为抛出异常,调用者不一定会检查返回值。

Replace Exception with Test:以测试取代异常

对于一个可以预先检查的条件,不要抛出异常,而应该用if先对其进行检查。

6. 简化条件表达式

Decompose Conditional:分解条件表达式

如果有一个复杂的if-then-else语句,则将if、then、else语句提取为单独的方法。

Consolidate Conditional Express:合并条件表达式

如果有一系列的条件执行相同的逻辑,则将这些条件提炼为一个返回Boolean的独立方法,然后在一个条件测试语句中调用这个方法。

Consolidate Duplicate Conditional Fragment:合并重复的条件片段

如果在条件表达式的每个分支上都有一段相同的代码,则将这段代码提取到条件表达式之外

Remove Control Flag:移除控制标记

在一系列Boolean表达式中,如果某个变量带有控制标记的作用,则移除这个变量,使用break或者return语句代替。

Replace Nested Conditional with Guard Clause:以卫语句取代嵌套条件表达式

条件表达式通常有两种表现形式,第一种形式是:所有分支都属于正常行为,第二种形式则是条件表达式提供的答案中只有一种是正常行为,其他都是不正常见的情况。如果两条分支都是正常行为,就应该使用if else。如果某个分支极其罕见,就应该单独检查该条件,并在该条件为真时从函数返回,这样的检查就是传说中的“卫语句”。

Replace Conditional with Polymorphic:以多态取代条件表达式

如果表达式根据不同对象类型选择不同的行为,则将这个表达式的每个分支放到一个子类内的覆写方法中,然后将原始的方法声明为抽象方法。

Introduce Null Object:引入Null对象

需要再三检查某物是否为null值,则考虑将null值替换为null对象(无效对象)。

Introduce Assertion:引入断言

如果某一段代码需要对程序状态做出某种假设,则以断言明确表现这种假设

7. 处理泛化关系

Pull Up Field:字段上移

如果子类有相同的字段,则迁移到父类

Pull Up Method:方法上移

如果子类有功能相同的方法,则迁移到父类

Pull Up Constructor Body:构造方法体上移

将子类构造方法体中相同的部分提取到父类的构造方法,子类使用super调用这个构造方法。

Push Down Field:字段下移

将父类中只与部分子类相关的字段迁移到这些子类。

Push Down Method:方法下移

将父类中只与部分子类相关的方法迁移到这些子类。

Extract Subclass:提取子类

将类中只被某些实例用到的特性迁移到一个新的子类中。

Extract Superclass:提炼超类

如果两个类有相似的特性,则新建一个类作为这两个类的父类,并将那些相似的特性移动到这个新建的父类。

Extract Interface:提取接口

如果若干客户使用类接口中的同一子集,或者两个类有相同的部分,则将相同的子集提取到一个接口中。

Collapse Hierarchy:合并继承体系

如果子类和超类没有太大的差别,则合并到一个继承层次中。

Form Template Method:塑造模板方法

类似模板方法模式,如果子类中以相同的顺序执行某些类似的方法,则将操作顺序提取到一个方法,并放到父类,父类调用这些不同的操作,子类通过覆写这些方法实现差异。

Replace Inheritance with Delegation:以委托取代继承

就是使用组合代替继承。如果子类只使用超类接口的一部分,或者根本不需要继承而来的数据,则删除子类对父类的继承,而是在子类中新建一个超类对象。

Replace Delegate with Inheritance:以继承取代委托

就是用继承代替组合。如果经常需要为两个组合类编写极其简单的委托方法,也就是说一个类的行为基本上都委托给了另一个类,则直接让这个类继承另一个类更简便。

8. 重新组织数据

Self Encapsulate Field:自封装字段

为字段增加get/set方法,好处是可以增加校验,或者延迟初始化,而且子类通过覆写方法改变行为,但本类中,尤其是构造函数中是直接访问字段,还是使用get/set方法,是值得讨论的。

Replace Data Value with Object:以对象取代数据值

如果一个数据必须和其他数据和方法一起使用才有意义,则将该数据项改为类对象。

Change Value to Reference:将值改为对象引用

Change Reference to Value:将引用对象改为值对象

可以将对象分成两类:reference object(引用对象)和value object(值对象)。值对象有一个非常重要的特征:它们应该是不可变的。要在引用对象和值对象之间做选择有时并不容易,如果希望给一个对象加入一些可修改数据,并确保对任何一个对象的修改都能影响到所引用此对象,则需要使用引用对象。而在分布式和并发系统中,不可变的值对象则更有用,因为无需考虑它们的同步问题。

Replace Array with Object:以对象取代数组

如果一个数组的元素各自代表不同的东西,则以对象替换数组,对于数组中的每个元素,以一个字段来表示。

Duplicate Observed data:复制被监视数据

指的是基于MVC模式重构,抽取原来被内嵌在用户界面中的业务逻辑处理,使用户界面和处理业务逻辑的代码分开,然后使用观察者模式实现数据和用户界面的同步。

Change Unidirection Association to Bidirectional:将单向关联改为双向关联

两个类都需要使用对方特性,但其间只有一条单向连接。添加一个反向指针,并使修改函数能够同时更新2条连接。

Change Bidirectional Association to Unidirection:将双向关联改为单向关联

两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性,则去除不必要的关联。

Replace Magic Number with Symbolic Constant:字面常量取代魔法数

如果一个字面数值有特别含义,则创建一个常量,根据其意义命名,并将上述的字面数值替换为这个常量。

Encapsulate Field:封装字段

将类中存在的public字段改为为private,并且提供相应的访问函数。

Encapsulate Collection:封装集合

如果一个方法返回一个集合,则让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。

Replace Record with Data Class:以数据类取代记录

你需要面对传统编程环境中的记录结构,比如一个遗留程序,或者是需要通过一个传统API来与记录结构交流,或是处理从数据库读出的记录,则为该记录创建一个类似外部记录的类,以便日后将某些字段和函数搬移到这个类中。

Replace Type Code with Class:以类来取代类型码

Replace Type Code with Subclass:以子类来取代类型码

Replace Type Code with State/Strategy:以状态/策略模式取代类型码

如果类中有一个数值类型码,但它并不影响类的行为,则以一个新的类替换该数值类型码(感觉用枚举类也不错)。如果一个类型会影响行为,则可以借助多态,以子类取代类型码来处理变化的行为。但如果类型的值在对象的生命周期内会发生变化,或者由于其他原因使得无法用子类,则可以使用状态模式和策略模式来重构。

Replace Subclass with Fields:以字段取代子类

如果各个子类的唯一差别只在“返回常量数据”的方法上,则在父类中新增一个字段,修改这些方法,使它们返回这个字段并删除子类,这个新增字段可以在对象构造时赋值。

9. 大型重构

Tease Apart Inheritance:梳理并分解继承体系

如果某个继承体系同时承担了两项责任,则建立两个继承体系,并通过委托关系让一个可以调用另一个。

Convert Procedural Design to Objects:将过程化设计转化为对象设计

将数据记录转化为对象,将大块的行为分解为小块,并移到相关的对象中。

Separate Domain from Presentation:分离领域和展示

实际指按照MVC模式重构程序

Extract Hierarchy:提取继承层次

如果某个类做了大量的工作,并且一部分工作是以大量的条件表达式完成的,则建立继承体系,以一个子类表示一种特殊情况。

行走的code

要发表评论,您必须先登录