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

目录

1. 建立一组可靠的测试环境。

2. 列表

3.WWWH

4.代码的坏味道

5.重构列表

5.1重新组织函数

5.2 在对象之间搬移特性

5.3 重新组织数据

5.4 简化条件表达式

5.5 简化函数调用

5.6 处理概括关系

5.7 大型重构

5.8 重构,复用与现实


最基本要求:“不改变软件行为”;

保持代码易读、易修改的关键,就是重构。

什么是重构  (对现有代码整理改进使其易读易改

        所谓重构(refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。重构是一种经千锤百炼形成的有条不紊的程序整理方法,可以最大限度地减少整理过程 中引入错误的几率。本质上说,重构就是在代码写好之后改进它的设计。

目标

        可控且高效的进行重构。

问题

(1)重构的一般性原则、定义?What

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

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

(2)进行重构的原因?(为什么应该重构)Why

  •         重构改进软件设计;
            重构使软件更容易理解;
            重构帮助找到bug;
            重构提高编程速度;
  • (3)如何嗅出代码中的“坏味道”,以及如何运用重构清除这些坏味道?(该在什么地方重构)Where / How

(4)开源的测试框架?构筑测试环境?

重构的第一步

1. 建立一组可靠的测试环境。

        必须让测试有能力自我检验:(知道错误发生在何处)

        唯有写出人类容易理解的代码,才是优秀的程序员。

2. 列表

#

坏味道

常用重构

1

Alternative Classes with Different Interfaces(异曲同工的类),p85

Rename Method(273),

Move Method(142)

2

Comments

(过多的注释),p87

Extract Method(110),

Introduce Assertion(267)

3

Data Class

(纯稚的数据类),p86

Move Method(142),

Encapsulate Field(206),

Encapsulate Collection(208)

4

Data Clumps

(数据泥团),p81

Extract Class(149),

Introduce Parameter Object(295),

Preserve Whole Object(288)

5

Divergent Change

(发散式变化),p79

Extract Class(149)

6

Duplicated Code

(重复代码),p76

Extract Method(110), Extract Class(149),

Pull Up Method(322), Form Template Method(345)

7

Feature Envy

(依恋情结),p80

Move Method(142). Move Field(146). Extract Method(110)

8

Inappropriate Intimacy

(狎昵关系),p85

Move Method(142), Move Field(146),

Change Bidirectional Association to Unidirectional(200)

Replace Inheritance with Delegation(352),

Hide Delegate(157)

9

Incomplete Library Class

(不完美的库类),p86

Introduce Foreign Method(162)

Introduce Local Extension(164)

10

Large Class

(过大的类),p78

Extract Class(149),

Extract Subclass(330)

Extract Interface(341)

Replace Data Value with Object(175)

11

Lazy Class

(冗赘类),p83

Inline Class(154),

Collapse Hierarchy(344)

12

Long Method

(过长函数),p76

Extract Method(110)

Replace Temp With Query(120).

Replace Method with Method Object(135).

Decompose Conditional(238)

13

Long Parameter List

(过长参数列),p78

Replace Parameter with Method(292),

Introduce Parameter Objec1(295),

Preserve Whole Object(288)

14

Message Chains

(过度耦合的消息链),p84

Hide Delegate(157)

15

Middle Man(中间人),p85

Remove Middle Man(160)

 Inline Method(117)

Replace Delegation with Inheritance(355)

16

*Parallel Inheritance Hierarchies

(平行继承体系),p83

Move Method(142),

Move Field(146)

同步增减

17

Primitive Obsession

(基本类型偏执),p81

Replace Data Value with Object(175),

Extract Class(149),

Introduce Parameter Object(295)

Replace Array with Object(186)

Replace Type Code with Class(218)

Replace Type Code with Subclasses(223)

Replace Type Code with State/Strategy(227)

  

18

Refused Bequest

(被拒绝的遗赠),p87

Replace Inheritance with Delegation(352)

19

Shotgun Surgery

(霞弹式修改),p80

Move Method(142),

Move Field(146),

Inline Class(154)

20

Speculative Generality

(夸夸其谈未来性),p83

Collapse Hierarchy(344),

Inline Class(154),

Remove Parameter(277),

Rename Method(273)

21

Switch Statements

(switch惊悚现身),p82

Replace Conditional with Polymorphism(255),

Replace Type Code with Subclasses(223),

Replace Type Code with State/Strategy(227),

Replace Parameter with Explicit Methods(285),

Introduce Null Object(260)

22

Temporary Field

(令人迷惑的暂时字段),p84

Extract Class(149),

Introduce Null Object(260)

3.WWWH

What

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

Why

        重构的目的是使软件更容易被理解和修改。

     (1)重构改进软件设计;
     (2)重构使软件更容易理解;
     (3)重构帮助找到bug;
     (4)重构提高编程速度;

When/Where

        三次法则:事不过三,三则重构;
        添加功能时重构;(帮助理解)
        修补错误时重构;(错误重构的信号)
        复审代码时重构;团队设计复审,单个复审者代码复审。

How

        间接层的某些价值。

     (1)允许逻辑共享;
     (2)分开解释意图和实现;
     (3)隔离变化;
     (4)封装条件逻辑。多态

        修改接口

                留下旧函数,让旧接口调用新接口。

        难以通过重构手法完成的设计改动

                将不考虑'安全性'需求时构造起来的系统重构为具备良好安全性的系统。

重构与设计

        可运行的最简化系统+重构;

重构与性能

        三种编写快速软件的方法。

                时间预算法:实时系统;
                持续关注法;
                90%统计数据;

4.代码的坏味道

(1)重复代码

(2)过长函数

        如何确定该提炼哪一段代码?

                找注释;

                条件表达式和循环。

(3)过大的类

        Extract Class

        Extract Subclass

        Extract Interface

        Duplicate Observed Data (189) GUI类

(4)过长参数列

        Replace Parameter with Method

        Preserve Whole Object

        Introduce Parameter Object 引入参数对象

(5)发散式变化

        Extract Class

(6)霰弹式修改

        Move Method 和Move Field:创建类Inline Class

(7)依恋情结

        将总是一起变化的东西放在一块儿。

        保持变化只在一地发生。

(8)数据泥团

        Introduce Parameter Object

        Preserve Whole Object

(9)基本类型偏执

        在小任务上运用小对象:运用Replace Data Value with Object将原本单独存在的数据值替换为对象;

        要替换的数据值是类型码,而它并不影响行为:运用Replace Type Code with Class;

        有与类型码相关的条件表达式,可运用Replace Type Code with Subclass或Replace Type Code with State/Strategy

        有一组应该总是被放在一起的字段,可运用Extract Class

        在参数列中看到基本型数据,不妨试试Introduce Parameter Object

        从数组中挑选数据,可运用Replace Array with Object

(10)switch惊悚现身

        *Extract Method提炼独立函数;

        *Move Method 搬移到多态类;

        *决定是否使用Replace Type Code with Subclasses 或Replace Type Code with State/Strategy;

        *完成继承结构之后,运用Replace Conditional with Polymorphism。

        单一函数中有些选择事例,且并不想改动它们:Replace Parameter with Explicit Methods;

        选择条件之一是null,可以试试Introduce Null Object。

(11)平行继承体系

        重复性:当你为某个类增加一个子类,必须也为另一个类相应增加一个子类。如果你发现某个继承体系的类名称前缀和另一个继承体系的类名称前缀完全相同

        策略:让一个继承体系的实例引用另一个继承体系的实例。如果再接再励运用Move Method和Move Field(把所有需要修改的代码放进同一个类),就可以将引用端的继承体系消弭于无形

(12)冗赘类

        子类没有做足够的工作:Collapse Hierarchy;
        几乎没用的组件:Inline Class。

(13)夸夸其谈未来性

        某个抽象类其实没有太大作用:Collapse Hierarchy;

        不必要的委托可运用Inline Class除掉。

        函数的某些参数未被用上,可对它实施Remove Parameter;

        函数名称带有多余的抽象意味,应该对它实施Rename Method。

(14)令人迷惑的暂时字段
        对象内某个实例变量仅为某种特定情况而设。
                使用Extract Class (149)给这个可怜的孤儿创造一个家;
                还可以使用Introduce Null Object (260)在“变量不合法”的情况下创建一个Null对象,从而避免写出条件式代码。

(15)    过度耦合的消息链
        Hide Delegate:
            先观察消息链最终得到的对象是用来干什么的,看看能否以Extract Method (110)把使用该对象的代码提炼到一个独立函数中,再运用Move Method (142)把这个函数推入消息链。
(16)中间人
        过度运用委托:某个类接口有一半的函数都委托给其他类;
            使用Remove Middle Man直接和真正负责的对象打交道;
            “不干实事”的函数只有少数几个,可以运用Inline Method (117)把它们放进调用端。
            如果这些Middle Man还有其他行为,可以运用Replace Delegation with Inheritance (355)把它变成实责对象的子类,这样你既可以扩展原对象的行为,又不必负担那么多的委托动作。

(17)狎昵关系
        可以采用Move Method (142)和Move Field (146)帮它们划清界线;
        也可以看看是否可以运用Change Bidirectional Association to Unidirectional让其中一个类对另一个斩断情丝;
        可以运用Extract Class (149)把两者共同点提炼到一个安全地点;
        也可以尝试运用Hide Delegate (157)让另一个类来为它们传递相思情。
        继承往往造成过度亲密,运用Replace Inheritance with Delegation (352)让它离开继承体系。
(18)    异曲同工的类    
        两个函数做同一件事,却有着不同的签名,请运用Rename Method;
        不够,请反复运用Move Method (142)将某些行为移入类,直到两者的协议一致为止;
        如果你必须重复而赘余地移入代码才能完成这些,或许可运用Extract Superclass。

(19)不完美的库类
        如果你只想修改库类的一两个函数,可以运用Introduce Foreign Method;
        如果想要添加一大堆额外行为,就得运用Introduce Local Extension

(20)纯稚的数据类
        能隐藏的隐藏(数据/行为)
            Encapsulate Field
            Encapsulate Collection
            Remove Setting Method
            Move Method把那些调用行为搬移到Data Class;
            无法搬移整个函数,就运用Extract Method产生一个可被搬移的函数。然后可以运用Hide Method把这些取值/设值函数隐藏起来了。
(21)被拒绝的遗赠
        ***Push Down Method (函数下移)和Push Down Field (字段下移)把所有用不到的函数下推给那个兄弟;
        即使你不愿意继承接口,也不要胡乱修改继承体系,应该运用***Replace Inheritance with Delegation (352)来达到目的;

(22)    过多的注释
        如果你需要注释来解释一块代码做了什么,试试Extract Method (110);
        如果函数已经提炼出来,但还是需要注释来解释其行为,试试Rename Method (273);
        如果你需要注释说明某些系统的需求规格,试试Introduce Assertion (267)。

5.重构列表

    重构手法
            名称、概要、动机、做法、范例
    重构的基本技巧——小步前进、频繁测试    
    模式是你希望到达的目标,重构则是到达之路。

5.1重新组织函数

    Extract Method(提炼函数)
        Replace Temp with Query 往往是你运用Extract Method之前必不可少的一个步骤。
    *Inline Method(内联函数):函数本体代替函数调用,移除该函数;
    *Inline Temp(内联临时变量):展开;
    Replace Temp with Query(以查询取代临时变量)
    Introduce Explaining Variable(引入解释性变量):复杂算法运用临时变量来解释每一步运算的意义;
    Split Temporary Variable(分解临时变量):某个临时变量被赋值超过一次,考虑使用Split Temporary Variable将它分割成多个变量。
    *Remove Assignments to Parameters(移除对参数的赋值)
    Replace Method with Method Object(以函数对象取代函数):局部变量不好提取,攒个对象管理;

5.2 在对象之间搬移特性

        决定把责任放在哪儿
    Move Method
    Move Field
    Extract Class;臃肿类分离责任;
    Inline Class:类内“不负责任”部分提取;
    *Hide Delegate:隐式的求人办事(组合),提高封装,方便修改;
    *Remove Middle Man:去除过度委托,直接搞定;
    *Introduce Foreign Method:现事现办;
    *Introduce Local Extension:新增责任(继承||组合)

5.3 重新组织数据

        更轻松“处理数据”的重构手法
    Self Encapsulate Field:直接访问->访问函数访问;
    *Replace Data Value with Object:数据丰富(+值+操作),单拎出来处理;
Change Value to Reference(将值对象改为引用对象)
    Change Reference to Value(将引用对象改为值对象)
    Replace Array with Object
    *Replace Magic Number with Symbolic Constant *魔法数
    Change Unidirectional Association to Bidirectional
    Change Bidirectional Association to Unidirectional
    Duplicate Observed Data:桥梁数据;
    Encapsulate Field
    Encapsulate Collection
    Replace Record with Data Class;传统数据记录应用,创建“哑”数据对象;
    *Replace Type Code with Class 类型码
            不影响类行为的数值类型码
            保证合法的类型检查,提高可读性,从而减少Bug。
    Replace Type Code with Subclasses
            不可变但会影响类的行为的类型码;
            取代标识1:Switch或者if/else语句;
            取代标识2:特性与类型码关联;(影响类的行为)
            Replace Conditional with Polymorphism操作的前调;
            两个例外:(1)类型码发生变化;(2)继承体系已有;
            总结:
                   Replace Type Code with Subclasses 的主要作用其实是搭建一个舞台,与Push Down Method和Push Down Field配合,让Replace Conditional with Polymorphism得以一展身手。如果宿主类中并没有出现条件表达式,那么Replace Type Code with Class更合适,风险也比较低。
    *Replace Type Code with State/Strategy
            可变,影响类的行为无法通过继承手法消除的类型码;
            选择:
                   如果你打算在完成本项重构之后再以Replace Conditional with Polymorphism简化一个算法,那么选择Strategy模式比较合适;
                   如果你打算搬移与状态相关的数据,而且你把新建对象视为一种变迁状态,就应该选择使用State模式。
    Replace Subclass with Fields(以字段取代子类)

5.4 简化条件表达式

        “分支逻辑” 和“操作细节”分离。
        多态还有一种十分有用但鲜为人知的用途:通过Introduce Null Object去除对于null值的检验。
Decompose Conditional(分解条件表达式)
        从if、then、else三个段落中分别提炼出独立函数;
Consolidate Conditional Expression
        合并条件代码,有两个重要原因:使检查的用意清晰;为使用Extract Method做准备;
        不合并条件代码原因:各检查间的确彼此独立且用意清晰;
Consolidate Duplicate Conditional Fragments(合并重复的条件片段)
        重复代码搬移到条件表达式之外;
Remove Control Flag(移除控制标记)
        以break语句或return语句取代控制标记;提高可读性;
*Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式)
        尽早return,方便理解;
Replace Conditional with Polymorphism
        建立继承结构,有两种选择:
                Replace Type Code with Subclasses(较简单推荐)
                Replace Type Code with State/Strategy(推荐条件:(1)类型码需要变动,继承不适合;(2)或要重构的类已有子类;)
*Introduce Null Object
        多态,去除对于null值的检验。    (去除条件)
Introduce Assertion(引入断言)对程序状态做出某种假设。    
        检查“一定必须为真”的条件;
        好处:迫使你重新考虑这段代码的约束条件;帮助你理解代码正确运行的必要条件。

5.5 简化函数调用

        使接口变得更简洁易用的重构手法。
    Rename Method:揭示函数的用途;
    Add Parameter:从调用端得到更多信息;
    Remove Parameter:不再用果断移除,防止用者做无用功;
    Preserve Whole Object:同一对象的多个值被当作参数传递;
    Introduce Parameter Object:同时出现的参数太多,整理成对象替代;
    Replace Parameter with Method:避免传递参数;
    Replace Parameter with Explicit Method:参数决定行为,各参数值独立成函数;
    * Parameterize Method:相似函数以参去重;
    Separate Query from Modifier:查/改分离
        很有价值的习惯:明确地将“修改对象状态”的函数(修改函数)和“查询对象状态”的函数(查询函数)分开设计;
        建立两个不同的函数,其中一个负责查询,另一个负责修改;
        一条好规则:任何有返回值的函数,都不应该有看得到的副作用;
    Hide Method
        降低函数可见度;
        为移除做准备;
    Remove Setting Method
    Replace Constructor with Factory Method
        由于构造函数只能返回单一类型的对象,因此你需要将构造函数替换为工厂函数
        **工厂函数也是Change Value to Reference的基础。
        Product Trader模式:操盘手模式:工厂+订阅;(避免超类必须知晓子类)
    Encapsulate Downcast:将向下转型封装隐藏起来,避免让用户做那种动作。(明确类型;尽量避免使用;)
    Replace Error Code with Exception(将“普通程序”和“错误处理”分开了,这使得程序更容易理解)
    Replace Exception with Test

5.6 处理概括关系

    继承关系
    Pull Up Field;共性上移
    Pull Up Method
    Push Down Method:特性下移
    Push Down Field
Pull Up Constructor Body:我们不会将构造函数往下推,因为Replace Constructor with Factory Method 通常更管用。
    Form Template Method:流程相同,内容不同;将若干函数的共同点和不同点分开。
    Extract Subclass:类中的某些行为只被一部分实例用到;特性下移;
        Extract Class是Extract Subclass之外的另一种选择,两者之间的抉择其实就是委托和继承之间的抉择。
        Extract Subclass通常更容易进行,但它也有限制:一旦对象创建完成,你无法再改变与类型相关的行为。
        但如果使用Extract Class,你只需插入另一个组件就可以改变对象的行为。此外,子类只能用以表现一组变化。如果你希望一个类以几种不同的方式变化,就必须使用委托。
    Extract Superclass;共性上移
    Extract Interface:想在类型系统中标示一小部分函数
        Extract Interface只能提炼共通接口,不能提炼共通代码。
        使用Extract Interface可能造成难闻的“重复”坏味道,幸而你可以运用Extract Class先把共通行为放进一个组件中,然后将工作委托该组件,从而解决这个问题。
        如果有不少共通行为,Extract Superclass会比较简单;
    Collapse Hierarchy:父子差异不大;继承体系中的某些类没有存在必要
    *Replace Inheritance with Delegation:不完全继承,+拥有功能的类为成员,自己主导需要的数据和接口;
    *Replace Delegation with Inheritance:经常为整个接口编写许多极简单的委托函数。

5.7 大型重构

    Tease Apart Inheritance(梳理并分解继承体系)
    Convert Procedural Design to Objects (将过程化设计转化为对象设计)
    Separate Domain from Presentation (将领域和表述/显示分离)
    Extract Hierarchy(提炼继承体系)

    《重构面向对象框架》
    “Refactoring Object-Oriented Frameworks.”
    
        试探法则

5.8 重构,复用与现实

        更好地支持框架开发过程;更普遍地支持变化过程
        重构使得设计思路更详尽明确。重构被用于开发框架、抽取可复用组件、使软件架构更清晰、使新功能的增加更容易。
        重构可以帮助你充分利用以前的投资,减少重复劳动,使程序更简洁有力。
        不重构的几个可能的原因:
        (1)不知道如何重构。
        (2)长远利益,短期体现不出来。
        (3)代码重构是一项额外工作,编写新功能是主线。
        (4)重构可能破坏现有程序。
        CRC        
        (1)类(Class)
        (2)职责(Responsibility)
        (3)协作(Collaborator)
        C++影响重构特性:    
            指针(算术运算)
            转型操作
            sizeof(object)
        指针和转型操作会造成“别名”,使你很难找到待重构对象的所有被引用点。上述这些特性暴露了对象的内部表现形式,违反了抽象原则。
        平衡短/长期利益
            重构和功能扩展“交错进行”;
        安全重构几种选择:
        (1)相信你自己的编码功力。
        (2)相信你的编译器能捕捉你遗漏的错误。
        (3)相信你的测试套件能捕捉你和编译器都遗漏的错误。
        (4)相信代码复审能捕捉你、编译器和测试套件都遗漏的错误。

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值