您的位置:首页 > 编程语言

【读书笔记】重构 改善既有代码的设计

2013-08-10 22:38 302 查看
重构就是在不改变软件可观察行为的前提下改变其内部结构,以提高其可理解性,降低其修改成本。
如果你发现自己需要为程序增加一个特性,而代码结构使你无法很方便地达成目的,那就应该重构了。Do't Repeat Yourself.
进行重构的第一个步骤永远是:为即将修改的代码建立一组可靠的测试环境。
重构技术就是以微小的步伐修改程序,如果你犯下错误,很容易便可发现它。
优秀的程序猿才能写出人类容易理解的代码:更改变量名是必要的。

重构原则

为何重构:重构改进软件设计,提高可理解性,帮助找到bug,提高编程速度。
何时重构:第一次做某事只管去做,第二次做类似的事情会产生反感,第三次在做类似的事情,重构吧!重构应该随时随地进行,不应该为了重构而重构。添加功能时,修补错误时,复审代码时。
计算机科学的任何问题,都可以通过增加一个间接层类解决:允许逻辑共享、分开解释意图和实现、隔离变化、封装条件逻辑。
重构的难题:数据库难以修改,修改接口,难以通过重构手法完成的设计改动。
何时不该重构:重构之前,代码必须能够在大部分情况下正常工作。重写或许比重构来得更划算。项目接近交付的时候,别重构了。
重构不能替代设计,重构带来的,是更简单的设计,同时又不失灵活性,这也降低了设计过程的难度。
重构与性能:编写快速软件的秘密就是:首先写出可调的软件,然后调整它以获得足够速度。编写快速软件的三种方法:时间预算法,持续关注法,在开发后期按照某个特定程序调整程序开发。短期来看,重构的确可能使软件变慢,但是使优化阶段的软件性能调整变得更容易,最终还得到好的效果。

代码的坏味道

重复代码

合而为一。
同一个类的两个函数类似,那就提取函数。两个互为兄弟的子类包含相同表达式,那就提取函数,对提取的代码推入超类中。如果代码之间只是类似,非完全相同,那么就得提取函数,建立模板了。
如果两个毫不相关的类有重复代码,考虑对其中一个提取类。

过长函数

分解函数。
如果函数内含有大量的参数和临时变量,那提取函数就不管用了,这是要用查询代替临时变量,引入参数对象和保留全部兑现都可以缩短过长的参数,最终的杀手锏是用方法对象代替方法。
提取哪一段代码呢?最好寻找注释吧,注释标出了代码用途和实现手法之间的语义距离。
条件表达式的提炼:分解条件。
循环的提炼:将循环和其内的代码提炼到独立函数中。

过大的类

提炼类:将相关的变量一起提炼到新类中。如果类中的数个变量有着相同的前缀或字尾,提取到一个组件中。如果这个组件适合作为一个子类,那么提取子类吧。提炼类和提炼子类可以多次使用。在提炼之前,先确定客户端如何使用它们。

过长参数列

如果向已有对象发送一条请求就可以取代一个参数,那么用方法来代替参数。这里的已有对象可以是函数所属类的一个字段,也可能是另一参数。也可以将来自同一对象的一堆数据收集起来,以该对象替换它们。如果某些数据缺乏合理的对象归属,那么就为它们创造一个“参数对象”。

发散式变化

如果某个类经常因为不同的原因在不同的方向上发生变化,这就是发散式变化。那么应该找出某特定原因而造成的所有变化,将它们提取到一个类中。

散弹式修改

与发散式变化相反。如果每遇到某种变化,都必须在许多不同的类中作出许多小修改。这就是散弹式修改。这时候要移动方法和移动成员变量把修改放进同一类中。如果没有合适的类可以安置这些代码,那就创造一个。通常运行内联类可以把一系列的相关行为放进同一类中。
发散式变化是指“一个类受多种变化影响”,散弹式修改指“一种变化依法多个类的修改”

依恋情结

函数对某个类的兴趣高于对自己所处的类的兴趣,那就移动函数吧。如果只是函数中的一部分代码,那就提取代码到新函数中,再移动新函数。
如果一个函数用到几个类的功能,就把它移动到拥有最多被使用的数据的类中。或者在此之前,你可以分解一下这个函数。总要将变化的东西放在一块儿。

数据泥团

那些总是绑在一起的数据,应该有属于它们的对象。

基本类型偏执

尝试用一些小对象代替数据值和类型码。

Switch惊悚现身

用多态代替它,这时就要建立新的对象。或者,用explicit方法代替参数。如果你的选择条件之一是null,那就引入null对象。

平行继承体系

每当你增加一个子类,也必须为另一个类增加子类。有两个继承体系。解决策略:让一个继承体系的实例引用另一继承体系的实例。如果再接再厉使用移动方法和移动成员变量,那就更有效果。

冗赘类

你所创建的每个类,都得要被理解。如果某些子类没有做足够的工作,那就Collapse Hierarchy。对于几乎没用的组件,用内部类。

夸夸其谈未来性

如果用不到,就不值得做。

令人迷惑的临时字段

将临时字段提取和函数类,或者引入null对象。

过渡耦合的消息链

解决方法:Hide Delegate。
先观察消息链的最终对象用来做什么的,看能否提取方法。

中间人

过渡使用委托,应该移除中间人,或者Replace Delegation with Inheritance。

Inappropriate Intimacy

拆散这种关系:移动方法和成员变量。或者提炼共同点到类。

异曲同工的类

移动函数,提取类

不完美的类库

如果你只想修改类库的一两个函数,那就引入外部函数。如果你想要添加一大堆额外行为,那就引入local extension。

纯稚的数据类

数据类是指拥有一些字段,以及用于访问这些字段的函数,除此之外,什么都没有。不会说话。
Encapsulate Field封装它的数据成员。

被拒绝的馈赠

子类应该继承超类的函数和数据,但如果它们不想或不需要继承,又该怎么办呢?
不要胡乱修改继承体系,应该应用Replace Inheritance with Delegation。

过多的注释

当你感觉需要些注释时,先尝试重构,试着让所有的注释变得多余。
如果你需要注释来解释一块代码,尝试着提取方法,如果函数已经提取出来,但还是要注释来解释,那就试着remove method。如果你需要注释来说明某些系统的需求规格,试试Introduce Assertion。

构筑测试体系

每个类都应该有一个测试函数,并以它来测试自己这个类。
确保所有测试都完全自动化,让它们检查自己的测试结果。
撰写测试代码的最有用的时机是在开始编程之前。编写测试代码就是在问自己:添加这个功能需要做些什么。
频繁地运行测试,每次编译请把测试也考虑进去,每天至少执行每个测试一次。
编写未臻完善的测试并实际运行,好过对完美测试的无尽期待。
考虑可能出错的边界条件,把测试火力集中在那儿。
当事情被人误应该会出错时,别忘了是否抛出了预期的异常。
不要因为测试无法捕捉所有的bug就不写测试,因为测试的确可以捕捉到大多数bug-----花合理时间捕捉大部分bug。

重构列表

每个重构手法都有一下五个部分:
Ø  名称,构造一个重构词汇表。
Ø  名称之后是一个简短概要:简单介绍这一重构手法的适用情境以及它做的事情。
Ø  动机:为什么要这个重构,什么情况下不该使用这个重构
Ø  做法:如何一步一步进行此重构
Ø  范例
重构的基本技巧----小步前进,频繁测试

重新组织函数

函数问题都源于Long Method(过长函数),一项重要的重构首发就是Extract Method,把一段代码从原先的函数中提取出来,放进一个单独函数中。Inline Method正好相反:将一个函数调用动作替换为该函数本体。如果在多次提炼之后,提炼到的某些函数没有做任何实质事情,或如果需要回溯到原先函数,那就需要Inline Method。
Extract Method最大困难就是处理局部变量,而临时变量则是其中一个主要的困难源头。处理一个函数时,可以用Replace Temp with Query去掉临时变量。如果有很多地方使用了某个临时变量,运用Split Temporary Method Object将它变得比较容易转换。
参数如果在函数内被赋值,那也很麻烦,可以使用Remove Assignment with Parameters

提炼函数 Extract Method

函数粒度小,被复用的机会就更大,也挺高了高层函数的可读性,函数的复写也相对容易些。
一个函数多长才合适,关键在于函数名称和函数本体之间的语义距离。
做法:
Ø  创造一个新函数,根据这个函数的意图对它命名(它“做什么”,而不是“怎么做”)
Ø  将提炼出的代码从源函数复制到新建的目标函数中
Ø  仔细检查提炼出的代码,看看其中是否引用了“作用于局限于源函数”的变量(包括局部变量和源函数参数)
Ø  检查被提炼代码段,看看是否有任何局部变量的值被改变。如果临时变量值被改变了,看看可否将被提炼代码处理为一个查询,将结果赋值给相关变量。如果被修改的变量不止一个,那就不能提取这段代码。可能需要Split Temporary Variable,在尝试提炼,也可以使用Replace Temp With Query消灭临时变量。
Ø  将被提炼代码中需要读取的局部变量,当作参数传给目标函数
Ø  处理完所有的局部变量后,进行编译
Ø  源函数中,将被提炼代码段替换为目标函数的调用(注意声明在目标函数中的临时变量,原有的声明是否还在代码段的外围)
Ø  编译,测试
Extract Method的难点在于局部变量,在源函数中,它在哪儿定义,是否为待提取的代码段使用,若使用,是只读或是写,若改变该变量之后,是否反悔源函数,这都要case by case。

内联函数 Inline Method

在函数调用点插入函数本体,然后移除该函数。
动机:使代码更可读。或者,在Replace Method WithMethod Object之前只用,先聚后散。
做法:
Ø  检查函数,确定它不具多态性
Ø  找出这个函数的所有被调用点
Ø  将这个函数的所有被调用点都替换为函数本体
Ø  编译,测试
Ø  删除该函数的定义
对于递归调用,多返回点,内联至另一对象中而该对象并无提供访问函数等情况,不要使用Inline Method手法。

内联临时变量 Inline Temp

将所有对该变量的引用动作,替换为对它赋值的那个表达式自身。
动机:Inline Temp多半是作为Replace Temp With Query的一部分使用的。单独使用Inline Temp的情况是,有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构手法。
做法:
Ø  检查给临时变量赋值的语句,确保等号右边的表达式没有副作用
Ø  如果这个临时变量未被声明为final,那就把它声明为final,然后编译
n  这个可以检查该变量是否真的只被复制一次
Ø  找到该变量的所有引用点,将它们替换为“为临时变量赋值”的表达式
Ø  每次修改后,编译,测试
Ø  修改完所有的引用点,删除该临时变量和赋值语句
Ø  编译,测试

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

你的程序以一个临时变量保存某一表达式的运算结果。将这个表达式提炼到一个独立函数中,将这个临时变量的所有引用点替换为对新函数的调用。此后新函数就可被其他函数使用。
动机:因为临时变量是暂时的,只能在函数内使用。
Replace Temp With Query往往是Extract Method之前必不可少的一步,尽量将局部变量替换为查询式。
临时变量只被赋值一次的情况比较简单,如果不是Split Temporary Variable或者Sequence Query from Modifier简化,再替换临时变量。
做法:
Ø  找出只被赋值一次的临时变量(若吵过一次,考虑SplitTemporary Variable)
Ø  将该变量声明为final
Ø  编译(确保该临时变量的确只被赋值一次)
Ø  将“对该临时变量赋值”的语句的等号右侧部分提取到一个独立函数中
n  首先将函数声明为private,日后需要使用,再扩大访问权限
n  确保提取的函数为任何副作用,不修改任何对象内容,如果有副作用,进行SequenceQuery from Modifier
Ø  编译,测试
Ø  在该临时变量是实施Inline Temp

引入解释性变量 Introduce Explaining Variable

你有一个复杂表达式,将该表达式(或其中一部分)的结果放进一个临时变量,以此变量来解释表达式用途。
动机:表达式有可能是复杂而难以阅读的。在条件逻辑中,尤其适用。
做法:
Ø  声明一个final类型变量,将待分解之表达式中的一部分动作的运算结果赋值给它
Ø  将表达式中的“运算结果”这一部分,替换为上述临时变量
Ø  编译、测试
Ø  重复上述过程,处理表达式的其他部分
在ExtractMethod需要花费更大工作量的时候,可以考虑Introduce Explaining Variable清理一下代码,然后考虑下一步是Replace Temp with Query还是Replace Method with Method Object。

分解临时变量 Split Temporary Variable

你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不用于收集计算结果。针对每次赋值,创造一个独立、对应的临时变量。

动机:如果被赋值超过一次,就意味着它在函数中承担了一个以上的责任。

做法:

Ø  在待分解临时变量的声明及其第一次被赋值处,修改其名称。(如果稍后的形式是[i+1],那它是用来收集计算结果的,不要赋值)

Ø  将新的临时变量声明为final

Ø  以该变量的第二次赋值动作为界,修改此前对该临时变量的所有引用点,让他们引用新的临时变量

Ø  在第二次赋值处,重新声明原先那个临时变量

Ø  编译、测试

Ø  逐次重复上述过程,每次都在声明处对临时变量改名,并修改下次赋值之前的引用点

Remove Assignment to Parameters(移除对参数的赋值)

代码对一个参数进行赋值。以另一个临时变量取代该参数的位置。

在java中,想要修改参数,是达不到预期结果的。Java只采用按值传递方式,即使是传递引用,也只是一个指针的拷贝。我可以修改参数对象的内部状态,但是对参数对象重新赋值是没有意义的。

 intdiscount(int inputVal, int quality, int yearToDate){

       if(inputVal> 50)

              inputVal=-2;

}

------------------>>>

int discount(int inputVal, int quality, intyearToDate){

       intresult = inputVal;

       if(intputVal>50)

              result= -1;

}

以函数对象取代函数 Replace Method with Method Object

你有一个大型函数,其中对局部变量的使用让你无法Extract Method,你可以将这个函数放进一个单独的对象,如此一来局部变量就成了对象内的字段,然后你可以在同一对象中将这个大型函数分解为多个小型函数。
做法
Ø  建立一个新类,根据待处理函数的用途,命名之
Ø 新类中建立一个final字段,用以保存原先大型函数所在的对象。同时,针对原函数的每个临时变量和每个参数,在新类中建立一个对应的字段保存之
Ø 在新类中建立一个构造函数,接收源对象及原函数的所有参数作为参数
Ø 在新类中建立一个compute函数
Ø 将原函数的代码复制到compute函数中,如果需要调用源对象的任何函数,请通过源对象字段调用
Ø 编译
Ø 将旧函数的函数本体替换为这样一条语句“创建上述新类的一个新对象,而后调用其中的compute函数”

替换算法 Substitute Algorithm

你想要把某个算法替换为另一更清晰的算法。将函数本体替换为另一个算法。

在对象之间搬移特性

搬移函数 Move Method

你的程序中,有个函数与其所驻类之外的另一个类有更多的交流,调用后者,或者被后者调用。那么,就在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数编程一个单纯的委托函数,或者是将旧函数完全移除。
做法:
Ø  检查源类中被源函数所使用的一切特性(字段和函数),考虑它们是否也应该被搬移
Ø  检查源类的子类和超类,看看是否有该函数的其他声明(如果有,可能无法搬移)
Ø  在目标类中声明这个函数(新的名称,对目标类更有意义)
Ø  将源函数的代码复制到目标函数中,调整后者,使得在新家中正常运行
Ø  编译目标类
Ø  决定如何从源函数正确引用目标对象
Ø  编译、测试
Ø  决定是否删除源函数,或把它当做一个委托函数留下来
Ø  如果要移除源函数,请将源类中对源函数的所有调用,都替换为对目标函数的调用
Ø  编译、测试

搬移字段 Move Field

你的程序中,某个字段被其驻类之外的另一个类更多的引用。在目标类建立一个字段,修改源字段的所有用户,令它们改用新字段。
做法:
Ø  如果字段的访问级是public,那就用Encapsulate Field封装起来(如果你有可能移动那些频繁访问该字段的函数,或如果有许多函数访问某个字段,先使用Self Encapsulate Field 也会有帮助)
Ø  编译、测试
Ø  在目标类中建立与源字段相同的字段,并同时建立相应的set/get
Ø  编译目标类
Ø  决定如何在源对象中引用目标对象
n  首先看是否有一个现成的字段或函数可以助你得到目标对象,如果没有,看能否建立这样一个函数。如果还是不行,就在源类中新建一个字段来存放目标对象。
Ø  删除源字段
Ø  将所有对源字段的引用替换为对某个目标函数的调用
n  如果源字段不是private的,就必须在源类的所有子类中查找源字段的引用点,替换
Ø  编译,测试

提炼类 Extract Class

某个类做了应该两个类做的事。建立一个新类,将相关的字段和函数搬移到新类中。
动机:单一职责原则!
做法:
Ø  决定如何分解类所负的责任
Ø  建立一个新类,用以表现从旧类中分离出的责任(注意新类和旧类的命名问题)
Ø  建立“从旧类访问新类”的连接关系(有可能是双向连接,在真正需要之前,不要建立“新类到旧类”的连接)
Ø  对于你想搬出的每一个字段,运用Move Field
Ø  每次搬移之后,编译、测试
Ø  使用Move Method将必要函数搬移到新类。先搬移较低层函数(也就是被调用次数多于调用次数的),再搬移较高层函数
Ø  每次搬移之后,编译、测试
Ø  检查,精简每一个类的接口(若是双向连接,可否单向)
Ø  决定是否公开新类

将类内联化 Inline Class

某个类没有做太多事情,将这个类的所有特性搬移到另一个类中,然后移除原类。
与Extract Class相反,如果一个类不再承担足够责任,就没有单独存在的理由。
做法
Ø  在目标类上声明源类的public协议,将其中所有函数委托至源类
n  如果以一个独立接口标识源类函数更合适的话,就应该在内联之前先使用ExtractInterface
Ø  修改所有源类的引用点,改为引用目标类
n  源类声明为private,以斩断包之外的所有引用可能。同时修改源类的名称,帮助捕捉隐藏的引用点
Ø  编译、测试
Ø  运用Move Method和Move Field,将源类的特性搬移到目标类
Ø  为源类举行一个简单的“丧礼”

隐藏“委托关系” Hide Delegate

客户通过一个委托类调用另一对象。在服务器上建立客户所需的所有函数,用以隐藏委托关系。(只是隐藏,中间人还在)
动机:封装,每个对象应该尽量少的了解系统的其他部分。
做法:
Ø  对于每个委托关系中的函数,在服务对象端建立一个简单的委托函数
Ø  调整客户,令它只调用服务对象提供的函数(如果使用者和服务提供者不在一个包,还要考虑委托函数的访问权限)
Ø  每次调整后,编译、测试
Ø  如果将来不再有客户调用受托类,考虑移除服务对象中的相关访问函数
Ø  编译、测试

移除中间人 Remove Middle Man

某个类做了过多的简单委托动作。让客户直接调用受托类。
做法:
Ø  建立一个函数,用以获得受托对象
Ø  对于每个委托对象,在服务类中删除该函数,并让需要调用该函数的客户转而调用受托对象
Ø  处理每个委托对象后,编译、测试

引入外加函数 Introduce Foreign Metho

你需要为提供服务的类增加一个函数,但你无法修改整个类。在客户类中建立一个函数,并以第一参数形式传入一个服务器实例。
如果你为一个服务类建立大量的外加函数,就不该使用本项重构手段,考虑Introduce Local Extension。

做法:
Ø  在客户端建立一个函数,提供你需要的功能(这个函数不应该调用客户端的任何特性,如果它需要一个值,那就当做参数传递给它)
Ø  以服务类实例作为该函数的第一个参数
Ø  将该函数注释为“外加函数,应该在服务类实现”(如果有机会将外加函数添加到服务类中,你可以轻松找到它们)

引入本地扩展 Introduce Local Extension

你需要为服务类提供一些额外函数,当你无法修改这个类。建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类或包转类。

重新组织数据

自组织数据 Self Encapsulate Field

你直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。为这个字段建立set/get函数,并只这些函数来访问字段。
间接访问的好处是,子类可以通过覆写函数来改变获取数据的途径,支持更灵活的数据管理方式,例如延迟初始化。直接访问的好处就是可读性。

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

你有一个数据项,需要与其他数据和行为一起使用才有意义,那就把数据项变成对象。
做法
Ø  为待替换数值新建一个类,在其中声明一个final字段,其类型和源类中的待替换数值类型是一致的。然后在新类中加入这个字段的取值函数,再加上一个接受此字段为参数的构造函数
Ø  编译
Ø  将源类中的待替换数值字段的类型改为前面新建的类
Ø  修改源类中该字段的取值函数,令它调用新类的取值函数
Ø  如果源类构造函数中用到这个待替换字段(多半是赋值动作),我们就修改构造函数,令它改用新类的构造函数来对字段进行赋值工作
Ø  修改源类中待替换字段的设值函数,令它为新类创建一个实例
Ø  编译、测试
Ø  现在,你可能需要对新类使用Change Value toReference

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

你从一个类衍生出许多彼此相等的实例,希望将它们替换为同一个对象。将这个值对象变成一个引用对象。
做法:
Ø  使用Replace Constructor With FactoryMethod
Ø  编译、测试
Ø  决定由什么对象负责提供访问新对象的途径(可能是一个静态字典)
Ø  决定这些引用对象应该预先创建好,还是动态创建
Ø  修改工厂函数,令它返回引用对象
Ø  编译、测试

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

你有一个引用对象,很小且不可变,不易管理。将它变成一个值对象。

做法:

Ø  检查重构目标是否为不可变对象,或是可否修改为不可变对象(可变的话,就可以RemoveSetting Method,直至它不可变。可变的对象无法使用这项重构技术)

Ø  建立equals和hashCode函数

Ø  编译、测试

Ø  考虑是否可删除工厂函数,将构造函数声明为public

以对象取代数组 Replace Array with Object

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

string[] row=new String[3];

row[0]="Arsenal";

row[1]="15"

--------->

Performance row=new Performance();

row.setName("Arsenal");

row.setWins("15");

做法:

Ø  新建一个类表示数组拥有的信息,在其中以一个public字段保存原先的数组

Ø  修改数组的所有用户,让它们改用新类的实例

Ø  编译、测试

Ø  逐一为数组元素添加set和get函数,根据元素的用途为其命名。修改客户端代码,通过访问函数访问。每次修改后,编译、测试

Ø  当所有对数组的直接访问都转而调用访问函数后,将新类中保存该数组的字段设为private

Ø  编译

Ø  对数组的每个元素,在新类中创建一个类型相当的字段,修改他的访问函数,改用上述的新建字段

Ø  每修改一个元素,编译、运行

Ø  所有元素有了相应字段后,删除该数组

复制“被监视数据” Duplicate Observed Data

你有一些field数据置身于GUI控件之中,而field函数需要访问这些数据。将该数据复制到一个field对象之中。建立一个Obeserver模式,用以同步field对象和GUI对象内的重复数据。

将单向关联改为双向关联 Change Unidirectional Association toBidirectional

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

做法:

Ø  在被引用类中增加一个字段,用以保存反向指针

Ø  决定由哪个类----引用端还是被引用端----控制关联关系

Ø  在被控端建立一个辅助函数,其命名应该表明它的用途

Ø  如果既有的修改函数在控制端,让它负责更新反向指针

Ø  如果既有的修改函数在被控端,就在控制端建立一个控制函数,并让既有的修改函数调用这个新建的控制函数

将双向关联改为单向关联 Change Bidirectional Association toUnidirectional

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

做法:

Ø  找出保存“你想要取出的指针”的字段,检查它的每一个用户,判断是否可以去除该指针。

n  不仅要检查直接访问点,也要检查调用这些点的函数

n  考虑有无可能不通过指针取得被引用对象,如果可能,可以对取值函数SubstituteAlgorithm,让客户在没有指针的情况下也可以使用该取值函数

Ø  如果客户使用了取值函数,先运用Self EncapsulateField将待删除字段自我封装,然后使用Substitute Algorithm。编译、测试

Ø  如果客户并未使用取值函数,那就直接修改待删除字段的所有引用点:改为以其他途径获得该字段所保存的对象。每次修改后,编译、测试

Ø  如果已经没有任何函数使用待删除的字段,移除对该字段的更新逻辑,然后移除该字段

n  如果有很多地方对此字段赋值,先用Self EncapsulateField使这些地方改用同一个设值函数。然后将这个函数的本体清空,如果可行,将此字段和设值函数及对它的调用,一并移除

Ø  编译、测试

以字面常量代替魔法数 Replace Magic Number with SymbolicConstant

你有一个字面数值,带有特别含义。创造一个常量,根据其意义为其命名,并将上述的字面数值赋给这个常量。

封装字段 Encapsulate Field

你的类中存在一个public字段,将其声明为private,并提供相应的访问函数。

动机:数据隐藏。增强模块化程度。

封装集合 Encapsulate Collection

有个函数返回一个集合,将这个函数返回该集合的一个只读副本,并在这个类中添加或移除集合元素的函数。

动机:函数返回一个集合会让用户得以修改集合,而集合拥有者却一无所知。另外,不应该为这个集合提供设置函数,而要提供添加和移除元素的函数。

做法:

Ø  加入为集合添加/移除元素的函数

Ø  将保存集合的字段初始化为一个空集合

Ø  编译

Ø  找出集合设置函数的所有拥有者,修改那个设置函数,让他使用新建的“添加/移除元素”函数

Ø  编译、测试

Ø  找出所有“通过取值函数获得集合并修改其内容”的函数,主义修改这些函数,改用“添加/移除元素”

Ø  修改取值函数自身,使它只返回一个只读副本

Ø  编译、测试

Ø  找出现有取值函数的所有用户,从早找出应该存在于集合所属对象内的代码。运用ExtractMethod和Move Method将这些代码移到宿主对象

Ø  编译、测试

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

你需要面对传统编程环境中的记录结构,为该记录创建一个“哑”数据对象

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

类之中有一个数值类型码,但它不影响类的行为。以一个新的类替换该数值的类型码。

做法:

Ø  为类型码建立一个类

Ø  修改源类的实现,让它引用新建的类

Ø  编译、测试

Ø  对于源类中每一个使用类型码的函数,相应建立一个函数,让新函数使用新建的类

Ø  逐一修改源类用户,让让他们使用新接口

Ø  删除使用类型码的旧接口,并删除保存旧类型码的静态变量

Ø  编译、测试

以子类取代类型码 Replace Type Code with SubClass

你有一个不可变的类型码,它会影响类的行为,以子类取代这个类型码。

动机:把“对不同行为的了解”从类用户那儿转移到类自身。

做法:

Ø  使用Self Encapsulate Field将类型码自我封装起来,如果类型码被传递给构造函数,那就将构造函数替换为工厂函数

Ø  为类型码的每一个数值建立一个相应的子类。让每个子类中覆写类型码的取值函数,使其返回相应的类型码值

Ø  每新建一个新的类,编译、测试

Ø  从超类中删掉保存类型码的字段,将类型码访问函数声明为抽象函数

Ø  编译、测试

以State/Strategy代替类型码 Replace Type Code with State/Strategy

你有一个类型码,它会影响类的行为,但你无法通过继承手法消除它,以状态对象代替类型。

做法:

Ø  使用self Encapsulate Field封装类型码

Ø  新建一个类,根据类型码的用途为其命名。这就是一个状态对象

Ø  为这个新类添加一个子类,每个子类对应一种类型码

Ø  在超类中建立一个抽象的查询函数,用以返回类型码。在每个子类中覆写该函数,返回确切的类型码

Ø  编译

Ø  源类中建立一个字段,用以保存新建的状态对象

Ø  调整源类中负责查询类型码的函数,将查询对象转发给状态对象

Ø  调整源类为类型码设置的函数,讲一个恰当的状态对象子类赋值给“保存状态对象”的那个字段

Ø  编译、测试

以字段取代子类 Replace SubClass with Field

你的各个子类的唯一差别只在“返回常量数据”的函数上,修改这些函数,使它们返回超类中的某个(新增字段),然后销毁子类。

做法:

Ø  对所有的子类使用Replace Constructor withFactory Method

Ø  如果有任何代码直接引用子类,令它改为引用超类

Ø  针对每个常量函数,在超类中声明一个final字段

Ø  为超类声明一个protected构造函数,用以初始化这些新增字段

Ø  新建或修改子类构造函数,使它调用超类的新增构造函数

Ø  编译、测试

Ø  在超类中实现所有的常量函数,令它们返回相应字段值,容纳后将该函数从子类中删除

Ø  子类中所有的常量函数全部删除后,使用Inline Method将子类构造函数内联到超类的工厂函数中

Ø  删掉子类

Ø  编译、测试

简化条件表达式

分解条件表达式 Decompose Conditional

你有一个复杂的条件(if-then-else)语句,从if,then,else三个段落中分别提炼出独立函数。

合并条件表达式 Consolidate Conditional Expression

你有一系列的条件测试,都得到相同结果。将这些测试合并为一个条件表达式,并将这些条件表达式提炼成一个独立的函数。

合并重复的条件片段 Consolidate Duplicate ConditionalFragments

在条件表达式的每个分支上有着相同的一段代码。将这段代码搬到条件表达式之外。

if()

{

dosomething1();

send();

}

else

{

dosomething2();

send();

}

移除控制标记 Remove control Flag

在一系布尔表达式中,某个变量带有“控制标记”的作用,以break或return取代控制标记。

动机:单一入口

以卫语句取代嵌套条件表达式 Replace Nested Conditional withGuard Clauses

函数中的条件逻辑使人难以看清正常的执行路径,使用卫语句表现所有的特殊情况。

多态取代条件表达式Replace Conditional with Polymorphism

有个条件表达式根据类型的不同而选择不同的行为。将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。

引入Null对象 Introduce Null Object

如果你要再三检查某对象是否为null,那就将null替换为null对象

引入断言(Introduce Assertion)

当某段代码需要对程序状态做出某种假设,以断言明确表示这种假设。

P.S. 断言的失败应该导致一个非受控异常(unchecked exception),最后软件成品中,会删除所有的断言。

简化函数调用

函数改名 Rename Method

如果函数名字未能揭示函数的用途,那就修改函数名称吧。

添加参数 Add Parameter

某个函数需要从调用段得到更多信息,为此函数添加一个对象参数,让该对象殆尽函数所需要的信息。

移除参数 Remove Parameter

函数本体不再需要某个参数,将该参数去除。

将查询函数和修改函数分离 Separate Query from Modifier

某个函数既返回对象状态值,又修改对象状态。建立两个不同的函数,set和get

令函数携带参数 Parameterize Method

若干函数做了类似的工作,但在函数本体中却包含了不同的值。那就建立一个单一的函数,以参数表达那些不同的值。

以明确函数取代参数 Replace Parameter with Explicit Method

有个函数,其中完全取决于参数值而采取不同行为。针对该参数的每一个可能值,建立一个独立函数。

保持对象完整 Preserve whole Object

从某个对象中取出若干值,将它们作为某一次函数调用时的参数。改为传递整个对象。

以函数取代对象 Replace Parameter with Methods

对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接收该函数的函数本身也能够调用前一个函数。那就让参数接收者去除该向参数,直接调用前一个函数。

引入参数对象 Introduce Parameter Object

某些参数总是很自然的同时出现,以一个对象取代这些参数

移除设值函数 Remove Setting Method

类中的某个字段应该在对象被创建时设值,然后就不再改变。那就去掉该字段的所有设值函数。

隐藏函数 Hide Method

有一个函数,从来没有被其他任何类用到。将这个函数设为private。

以工厂函数取代构造函数 Replace Constructor with FactoryMethod

你希望在创建对象时不仅仅是简单的构建动作,那就将构造函数替换为工厂函数。

封装向下转型 Encapsulate DownCast

某个函数返回的对象,需要由函数调用者执行向下转型。将向下转型动作移到函数中。

以异常取代错误码 Replace Error Code with Exception

某个函数返回一个特定的代码,用以表示某种错误情况。改用异常。

以测试取代异常 Replace Exception with Test

面对一个调用者可以预先检查的条件,你抛出了一个异常。修改调用者,使它在调用函数之前先做检查。

处理概括关系

字段上移 Pull Up Field

两个子类有相同的字段,将该字段移到超类

函数上移 Pull Up Method

有些函数,在各个子类中有着完全相同的结果。将该函数移到超类

构造函数本体上移 Pull Up Constructor Body

各个子类中有些构造函数,函数本体几乎完全一致。那就找超类中新建一个构造函数,并在子类构造函数中调用它。

函数下移  PushDown Method

超类中的某个函数只与部分子类有关。将这个函数移到相关的那些子类中去。

字段下移 Push Down Field

超类中的某个字段只被部分子类用到。将这个字段移到需要它的那些子类中去。

提炼子类 Extract SubClass

类中的某些特性只被某些实例用到。新建一个子类,将上面所说的一部分特性移到子类中。

提炼超类 Extract SuperClass

两个类有相似特性。为这两个类建立一个超类,将相同特性移到超类。

提炼接口 Extract Interface

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

折叠继承体系 Collapse Hierarchy

超类和子类并无太大区别,将它们合为一体。

塑造模板函数 Form TemPlate Method

有些子类,其中的某些函数以相同的顺序执行相似的操作,但各个操作的细节上有所不同。将这些操作分别放进独立函数中,并保持它们都有相同的签名,于是原函数也变的相同了,然后将源函数上移至超类。

以委托代替继承 Replace Inheritance with Delegation

某个子类只使用了超类接口中的一部分,或者根本就不需要继承而来的数据。那就在子类中新建一个字段用以保存超类;调整子类函数,令它该而委托超类;然后去掉两者之间的继承关系。

以继承代替委托 Replace Delegation with Inheritance

你在两个类中使用委托关系,并经常为整个接口编写许多极其简单的委托函数。那就让继承代替委托吧。

大型重构

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

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

将过程化设计转为对象设计 Convert Procedural Design toObjects

将数据记录变成对象,将大块的行为分成小块,并将行为移入相关对象之中。

将领域和表述/显示分离

某些GUI类中包含了field逻辑。将field逻辑分离出来,为他们建立单独的field类。

提炼继承体系 Extract Hierarchy

某个类做了太多工作,其中一部分工作是以大量条件表达式完成的。建立继承体系,以一个子类表示一种特殊情况。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  程序设计