《重构――改善既有代码的设计》读书笔记

重构――改善既有代码的设计

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 MethodExtract ClassPull Up MethodForm Template Method

2. Long Method:过长函数

重构方法:Extract MethodReplace Temp with QueryReplace Method with Method ObjectDecompose Conditional

3. Large Class:过大的类

重构方法:Extract ClassExtract SubclassExtract InterfaceReplace Data Value with Object

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

重构方法:Replace Parameter with MethodIntroduce Parameter ObjectPreserve Whole Object

5. Divergent Change:发散式变化

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

重构方法:Extract Class

6. Shotgun Surgery:霰弹式修改

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

重构方法:Move MethodMove FieldInline Class

7. Feature Envy:依恋情结

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

重构方法:Move MethodMove FieldExtract Method

8. Data Clumps:数据泥团

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

重构方法:Extract ClassIntroduce Parameter ObjectPreserve Whole Object

9. Primitive Obsession:基本类型偏执

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

重构方法:Replace Parameter with MethodExtract ClassIntroduce Parameter ObjectReplace Array with ObjectReplace Type Code with ClassReplace Type Code with SubclassReplace Type Code with State/Strategy

10. Switch StatementSwitch语句

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

重构方法:Replace Conditional With PolymorphicReplace Type Code with SubclassReplace Type Code with State/StrategyReplace Parameter with Explicit MethodsIntroduce Null Object

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

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

重构方法:Move MethodMove Field

12. Lazy Class:冗余类

无用的类

重构方法:Inline ClassCollapse Hierarchy

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

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

重构方法:Collapse HierarchyInline ClassRemove ParameterRename Method

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

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

重构方法:Extract ClassIntroduce Null Object

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

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

重构方法:Hide Delegate

16. Middle Man:中间人

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

重构方法:Remove Middle ManInline MethodReplace Delegation with Inheritance

17. Inappropriate Intimacy:狎昵关系

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

重构方法:Move MethodMove FieldChange Bindirectional Association to UnidirectionalReplace Inheritance with DelegationHide Delegate

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

类名不同但功能相似。

重构方法:Rename MethodMove Method

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

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

重构方法:Introduce Foreign MethodIntroduce Local Extension

20. Data Class:纯稚的数据类

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

重构方法:Move MethodEncapsulate FieldEncapsulate Collection

21. Refused Bequest:被拒绝的遗赠

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

重构方法:Replace Inheritance with Delegation

22. Comments:过多的注释

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

重构方法:Extract MethodIntroduce 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语句,则将ifthenelse语句提取为单独的方法。

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:提取继承层次

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


本文出自 “自强不息,厚德载物” 博客,谢绝转载!

你可能感兴趣的:(重构)