重构笔记(二)——重构方法

第六章重新组织函数

本章的重构主要是针对函数进行的,假设原函数为A,原类为A,新函数和新类为B。在程序中,最麻烦的是long method,通过extract method分解函数,如果分解时,发现变量不好处理,使用 splict temporary variable处理赋值多次问题;使用replace temp query减少变量数量;如果实在搞不定,就是用 replace method with method object

Extract Method

动机:

       函数太长,需要注释。

       判断函数长度的标准是代码的清晰度,就算函数名称比提炼出来的代码还长也无所谓

做法:

       1.处理临时变量,有三种情况:

1.       变量只在A函数中出现,则将该语句放回A函数中

2.       变量在B中只读,将其作为函数B的参数

3.       变量在B中被赋值,如果不被其他地方使用,可以将该变量一起提炼到B函数中;如果变量在B调用后不使用,在B函数中直接修改即可;如果变量在B调用后还要使用,让B函数返回该变量;如果有多个返回的变量,挑选提炼的代码,保证只有一个值返回。

4.       如果临时变量过多,先使用 replace temp with query,减少临时变量的个数

5.       如果提炼还是困难,考虑使用replace method with method object

6.       使用eclipse extract method方法

Inline Method

       算是 Extract Method的逆操作

动机:

1.       内部代码和函数名同样清晰易读。

2.       有一组不太合理的函数,先组合为大函数后,重新提炼

3.       使用了太多的间接层

做法:

1.       检查函数的多态性:在各子类中查找函数名,若有实现,则不能重构

2.       使用eclipse inline

Replace Temp with Query

       将表达式替换为一个函数

动机:

1.       提高清晰度

2.       同一类中所有函数都可使用

做法:

1.       将变量声明为final

2.       将表达式 extract method

3.       使用方法替换变量的使用,并删除变量

Split temporary variable

       某个临时变量被赋值超过一次,就是临时变量有多种涵义。

做法:

1.       声明变量为final,编译

2.       修改到下次赋值前的变量名,使其符合涵义

3.       后边赋值处,同12操作

Remove Assignments to Parameters

       解决问题:程序中对参数进行了设置

做法:

       原始的  int  fun(int val) { val=32; }

       修改后: int fun(int val) {  int temp=val;  temp=32; }

Replace Method With Method Object

       使用对象来替代函数,函数分解的终极杀器

动机:

       很难拆分一个长函数

做法:

1.       新建类B,包含final字段A的对象

2.       构建computer() 函数,原函数的参数和临时变量作为类B字段

3.       A类对象和函数的参数作为构造函数的参数传入

4.       使用:创建B类对象b; b.computer()替换原函数的调用

第七章在对象间搬移特性

       在程序设计中,决定责任放在哪是很重要,最常使用三个:通过extract class分解大类;先使用move field,再使用 move method,完成责任的迁移。

       当不能访问源代码时,如果修改较少使用introduce foreign method;否则使用 introduce local extension

Extract class

       某个类做了应该两个类做的事情

动机:

       某些数据和函数、几项数据总是一同出现,一个测试:如果搬移了某些字段和函数,是否有其他字段或函数变得无意义

       某些特性需要一种方式来子类化,某些特性需要另一种方式子类化

做法:

1.       建立新类B

2.       建立旧类A访问B的方式

3.       使用move methodmove field完成搬移

Move field

       该字段被其他类过多地用到

       如果fieldpublic,先使用 encapsulate field处理

Move method

       使用前,先使用extract method处理

动机:

       使用move field

       其他类用的更多

做法:

1.       检查源类的子类和超类,如果有多态性,或许无法搬移

2.       在目标类中构造新函数

3.       修改原函数为指向目标函数的委托函数

4.       修改外部的调用方式

5.       移除原函数的声明

Inline class

       将这个类的所有特性搬移到另一个类中,然后删除这个类

动机:

       通常是前期的重构动作移走了这个类的责任

方法:

       使用eclipse菜单项

Hide Delegate

       对调用端封装系统内部结构

Remove middle man

       单个类做了过多的简单委托动作

       Hide Delegate是逆操作,关键是把我合适的隐藏程度

Introduce Foreign Method

动机:

无法修改源代码

要修改的函数《=2

做法:

       在调用端建立函数,并以第一参数传入服务类的实例。如:

       原始的: Date new=new Date(previous.getYear(),previous.getMonth(),previous.getDate()+1)

       修改后:Date new=nextDay(previous);

              Private function Date nextDay(Date previous) {

                     Return new Date(previous.getYear(),previous.getMonth(),previous.getDate()+1)

}

Introduce Foreign Class

动机:

无法修改源代码

要修改的函数》2

做法:

       建立新类,有final的服务类的字段

第八章重新组织数据

       找出数据的get方法的用户,从中找出应该存在于数据所在对象的代码,使用move fieldmove method处理

Self Encapsulate Field

       field建立get/set函数,通常作为其他重构方法的前戏

动机:

       在子类对fieldget/set进行override

Replace data value with object

       将数据变为对象

动机:

       有一个数据项,必须与其他数据和行为一起使用才有意义

做法:

1.       新建class B

2.       将相关数据项移入该class中,创建get

3.       通过构造函数传入值

4.       在原类、函数中,创建B类对象,通过get访问参数

Replace Array with Object

       数组每个元素意义不同,如:

       Row[0]=”Live” row[1]=”15”

       这种情况比较少见,就不考虑了,基本思路是建立明确意义的field,使用getset访问。

Change Bidirectional Association to Unidirectional

Change Unidirectional to Bidirectional Association

       没看懂

Replace Magic Number with Constant

       在程序中不要使用魔法数字,使用eclipse extract constants

Encapsulate Collection

       集合的get/set方法。

       修改为:  getCourses():Unmodifiable Set;  addCourse(c)  removeCourse(c)

       通过 Collection.unmodifiableXxx()得到集合的只读副本

Replace Type Code with Class

       类中有一个类型码,但不影响类的行为

动机:

       如类中的type字段

       可以对这个类型进行类型检查

做法:

1.       新建类PersonType,将type和常量都移入,及getType方法;

2.       Person中声明字段PersonType type,get/set方法

3.       调用处设置type person.setPersonType(PersonType.TEACHER)

4.       调用出获取typeperson.getPersonType().getType();

Replace Type Code with Subclass

       不变的类型码,会影响类的行为

动机:

       switch或者if else结构,根据不同的类型码执行不同的动作

       不适用:类型码在对象创建后发生改变;类已经有子类

       本重构多和 push down methodpush down field结合使用

做法:

1.       type的每个常量建立一个子类

2.       基类的getType为抽象函数

3.       各子类的getType返回具体的类型

Replace Type Code with Strategy/State

动机:

       类型码在对象创建后发生改变

类已经有子类

当完成本重构后,使用replace conditional with polymorphism选择strategy,打算移动与状态相关的数据,使用State模式。

做法:

1.       根据类型码作用建立类,如PersonType

2.       对类型码类添加子类,和getType函数

3.       在原类中建立PersonType type字段

Replace Subclass with Fields

       各自类的差别只在“返回常量数据”的函数本身,行为都一样

       最终效果类似于 Replace Type Code with Class

做法:

1.       将引用子类的代码改而引用超类

2.       构造 XXXType类,包含 type和对应个子类的常量字符串

3.       在基类中加入字段 XXXType type

4.       删除各子类

第九章简化条件表达式

Replace Nested Conditional with Guard Clauses

       条件表达式有两种情况:一种形式是所有分支都属于正常行为;一种形式是只有一个分支是正常,其他都不常见的。

       本重构主要处理第二种方式,对于罕见的分支,单独检查该条件,并在条件为真时立刻返回。

Decompose Conditional

if  elseif的条件表达式分别提取独立的函数

提炼出来的函数可读性更高一些

对于嵌套条件表达式,优先使用guard clauses重构

Consolidate Conditional Expression

对相同返回结果的条件进行合并,合并的条件是从语义上是否做为同一次检查

extract method做好准备

Consolidate Duplicate Conditional Fragments

条件表达式的每个分支上有一段相同的代码

做法:

如果共通代码位于中段,观察共同代码之前或之后的代码是否改变了什么东西,如果有改变,首先将共同代码移植表达式的起始处或尾端

如果共同代码不只一条语句,先使用extract method处理

Introduce Null Object

动机

       对空对象有较多的处理,比如对null的判断,是否为空执行不同的操作等。

       退而广之,可以对Special Case(某个类的特殊情况)进行处理

做法

1.       建立接口 Nullable { public Boolean isNull();  }

2.       为原类(Customer)建立子类 NullCustomer,都是先 Nullable接口,原类返回false,子类返回true

3.       在原始类(Customer)建立工厂函数 static Customer  newNull()

4.       找出所有返回类型为Customer的函数,将return null替换为return NullCustomer

5.       对所有的 customer==null ,替换为customer.isNull()

6.       空对象执行不同的操作,将该操作移入NullCustomer中作为重载函数。只有大多数调用端都要求作出相同的相应,这样才有意义

Introduce Assertion

对于函数的参数,如果一定为真(约束条件不满足,程序是否还能运行),使用assert确定。

存在重复的断言,使用extract method

第十章简化函数调用

Rename Method

      

做法:

       使用eclipse Change Method Signature

       当函数被子类或超类实现时:

1.       声明新函数,把旧函数代码放入新函数中

2.       旧函数调用新函数

3.       修改所有调用处,调用新函数

4.       旧函数加上 @depreciate

Add Parameter

动机:

       先考虑 Introduce Parameter ObjectReplace Parameter with Explict MethodMove Method

做法同上,可使用eclipseintroduce parameter

Remove Parameter

       Add 的逆操作,同上

Separate Query From Modifier

动机:   

如果一个函数既返回对象状态,又修改对象状态。

做法:

1.       查看原函数所有的返回值,将返回值和相关逻辑提取为函数 query

2.       Rename method原函数为modifier,返回类型为void

3.       修改调用端为 modifier; query;

Parameter Method

       行为略有不同

做法:

1.       新建带参数的函数

2.       原函数都调用该新函数

3.       如果要去掉原函数,则修改调用端

Replace Parameter with Explict Methods

参数值不同,执行的操作全不一样:针对参数的可能值建立明确的函数

Parameter Method的逆操作。

做法:

1.       针对参数的每种可能性,建立新函数

2.       修改原函数的调用点,调用新函数

Preserve Whole Object

       对象已经存在时,如果参数属于某一对象,传递该对象

做法:

1.       找出可以被对象替换的参数

2.       使用对象替换这些参数

3.       修改调用端

Introduce Parameter Object

       如果对象不存在,多项参数又同时出现,则定义对象,包含这些参数和相关函数,多和move method一起使用

       使用eclipseintroduce parameter object

Replace Parameter with Methods

该参数可以调用其他函数得到,则去除该参数,在函数内部调用其他函数

动机:

1.       本类函数可以计算出参数值,去掉,如果该函数依赖于调用端的某参数,则算了

2.       其他类函数计算得到,本类有哪个类的引用,去掉,如果没有引用,也不想加,则算了。

3.       参数是为了未来的灵活性,去掉

Remove Setting Method

       变量只在对象构造时改变,则去除set函数,并将变量设置为final

Hide Method

       该函数只在本类中使用,设为private

Replace Constructor with Factory Method    

1. 静态工厂具有名字
      i. 对于两个构造函数,如果参数类型和个数相同,则只能使用不同的顺序进行区分,而使用工厂函数可以为这两个构造函数指明不同的名称
      ii. 如果有多个构造函数,可考虑用静态工厂方法替换
2. 不要求创建新对象
      i. 当系统需要该类的N或1个实例,如果使用构造函数,则每次调用都会创建一个实例,可通过单例或多例模式重用已有实例
3. 返回原类型的子类型对象
      i. 对于一个继承体系,在基类中声明静态工厂方法,根据type返回不同的子类实例,且该子类为package 或 private,可以向客户端隐藏子类。
命名规范    
       getInstance     
       valueOf : 返回的实例与原有实例具有相同的值,如 Float 和float  , XXXConfig 和 XXX

 

Encapsulate Downcast

       将向下转型移到函数中,也就是让函数的返回值尽可能的具体,如 return  (Reading) readings.lastElements();

参数引起的错误处理

       有三种方式 error code, assert exception,我倾向于使用assert处理;如果该异常不是非常罕见,可以使用error code,剩下的就给 exception

第十一章处理概括关系

Pull Up Field

       将子类共有的字段移到超类中。

       判断若干字段是否重复,观察函数如何使用它。

       在上移前将它们的名称改成相同的

       使用eclipsepull up

Pull up Method

       在上移前将函数签名改成相同的。

使用eclipsepull up

Pull Down Method

       某个函数只与部分(而不是全部)子类相关:将函数移到相关子类中

       使用eclipsepull down

Extract Subclass

       发现类中的某些行为只被部分实例用到,有时候这种行为是通过类型码来区分。

使用eclipseextract class

Extract Superclass

       将共有部分上移到超类,使用extract superclass

Extract Interface

1.       若干客户使用类接口中的同一子集,或两个类的接口有部分相同

2.       某个类在不同的环境下扮演不同的角色,可以针对每个角色定义一个接口

3.       描述一个类的外部依赖接口,如Runnable

方法:

       Eclipse extract interface

Replace Inheritance with Delegation

只使用超类的一部分,或是根本不需要继承来的数据

方法:

1.       子类中新建一个字段保存超类

2.       调整子类函数,改为通过委托调用

3.       去掉两者的继承关系

Replace Delegation with Inheritance

编写许多简单的委托函数

       不能使用的:1)如果没有使用委托类的所有函数,就不应该使用继承,可以通过Remove Middle man让客户端直接调用受托函数;使用Extract SuperClassInterface)将共有部分提炼到超类;2)受托对象被多个对象共享,而且该对象是可变的

       多继承的实现:运用Extract Class先把共通行为放到一个组件时,通过委托调用该组件。

第十二章大型重构

       理解不到,暂时用不上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值