重构(Refactoring)技巧

重构(Refactoring)技巧  
本文简要整理重构方法的读书笔记及个人在做 Code Review 过程中,对程序代码常用的一些重构策略。通过适当的重构代码,的确可以显著提高代码的质量,令人赏心悦目。毫无疑问,这些重构策略均来自于 Martin Fowler 《重构-改善既有代码的设计》,只是如何在实际项目中灵活运用而已。(注:本文重构策略的名称及其大部分内容来自《重构-改善既有代码的设计》一书, Martin Fowler 著,侯捷等译)。
先看看重构的定义吧:
1 Refactoring means rewriting existing source code with the intent of improving its design rather than changing its external behavior. The focus of refactoring is on the structure of the source code, changing the design to make the code easier to understand, maintain, and modify. 来自 Borland Together 提供的文档,觉得这个定义很清晰明了。
2 )重构是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改,已改进程序的内部结构。-来自 Martin Fowler 的定义。
不过,我一般使用一些重构的工具,如 ReSharper for VS.Net v1.0 Borland Together for VS.Net v2.0 ,这些重构工具可以帮助你做很多事情,可以简化你许多工作,同时也可以避免出现一些错误。关于 ReSharper for VS.Net v1.0 的简单介绍,可以参考Rickie 以前的一篇 Posting体验ReSharper V1.0 for VS.Net 2003 - Part IVS.Net 2005已经内置了重构功能。不过,目前这些重构工具还远远不能涵盖各种重构方法,有总比没有好了。 , 体验ReSharper V1.0 for VS.Net 2003 - Part II】。另外
因此,掌握必要的重构技巧逐步成为对程序员基本的要求,重要的是在掌握这些技巧后,也有助于类库初期设计的质量,避免或减少代码的坏味道 (bad smell)
 
一、代码坏味道(Bad Smell in Codes)及其重构策略
1.尽量消除重复的代码,将它们合而为一
根据重复的代码出现在不同的地方,分别采取不同的重构的策略:
在同一个 Class 的不同地方:通过采用重构工具提供的 Extract Method 功能提炼出重复的代码, 然后在这些地方调用上述提炼出方法。
在不同 Subclasses 中:通过 Extract Method 提炼出重复的代码,然后通过 Pull Up Method 将该方法移动到上级的 Super class 内。
在没有关系的 Classes 中:通过对其中一个使用 Extract Class 将重复的代码提炼到一个新类中,然后在另一个 Class 中调用生成的新类,消除重复的代码。
 
2.拆解过长的函数
过长的函数在我们的日常代码中经常可见,在 C# 中常通过 #region #endregion 区隔为不同的功能区域。
重构策略:通过 Extract Method 将过长的函数按照功能的不同进行适当拆解为小的函数,并且给这些小函数一个好名字。通过名字来了解函数提供的功能,提高代码的理解性。
 
3.拆解过大的类
过大的类也经常见到,特别是类中含有大量的成员变量。
重构策略:通过 Extract Class 将一些相关成员变量移植到新的 Class 中,如 Employee 类,一般会包含有联系方式的相关属性(电话, Mobile ,地址, Zip 等等),则可以将这些移植到新的 EmployeeContact 类中。
 
4.过长的参数列
过长的参数列的主要问题是难以理解,并且难以维护。如果要增加新的参数或者删除某一参数,易造成参数前后不一致。
重构策略:如果可以通过向已存在的对象查询获取参数,则可通过 Replace Parameter with Method ,移除参数列,通过在函数内部向上述已存在的对象查询来获取参数。
如果参数列中若干参数是已存在对象的属性,则可通过 Preserve Whole Object 将这些参赛替换为一个完整对象,这样不仅提高代码的可读性,同时已易于代码今后的维护。
另外,还可以将若干不相关的参数,使用 Introduce Parameter Object 来创建一个新的参数类。不过,我个人觉得如果这些情况过多的话,会产生很多莫名其妙的参数类了,反而降低代码的可读性。
 
个人觉得前面 4 种坏味道比较显而易见,也比较容易处理。

 

现象:当某个 Class 因为外部条件的变化或者客户提出新的功能要求等时,每次修改要求我们更新 Class 中不同的方法。不过这种情况只有在事后才能觉察到,因为修改都是在事后发生的么(废话)。
重构策略:将每次因同一条件变化,而需要同时修改的若干方法通过 Extract Class 将它们提炼到一个新 Class 中。实现目标是:每次变化需要修改的方法都在单一的 Class 中,并且这个新的 Class 内所有的方法都应该与这个变化相关。
 
6 Shotgun Surgery (霰弹式修改)
现象:当外部条件发生变化时,每次需要修改多个 Class 来适应这些变化,影响到很多地方。就像霰弹一样,发散到多个地方。
重构策略:使用 Move Method Move Field Class 中需要修改的方法及成员变量移植到同一个 Class 中。如果没有合适的 Class ,则创建一个新 Class 。实现目标是,将需要修改的地方集中到一个 Class 中进行处理。
 
比较 Divergent Change (发散式变化)和 Shotgun Surgery (霰弹式修改):
前者指一个 Class 受到多种外部变化的影响。而后者指一种变化需要影响到多个 Class 需要修改。都是需要修理的对象。
 
7 Feature Envy (依恋情结)
现象: Class 中某些方法“身在曹营心在汉”,没有安心使用 Class 中的成员变量,而需要大量访问另外 Class 中的成员变量。这样就违反了对象技术的基本定义:将数据和操作行为(方法)包装在一起。
重构策略:使用 Move Method 将这些方法移动到对应的 Class 中,以化解其“相思之苦”,让其牵手。
 
8 Data Clumps (数据泥团)
现象:指一些相同数据项目( Data Items ),如 Class 成员变量和方法中参数列表等,在多个 Class 中多次出现,并且这些数据项目有其内在的联系。
重构策略:通过使用 Introduce Parameter Object (创建新的参数对象取代这些参数)或 Preserve Whole Object (使用已存在的对象取代这些参数),实现使用对象代替 Class 成员变量和方法中参数列表,清除数据泥团,使代码简洁,也提高维护性和易读性。
 
9 Primitive Obsession (基本型偏执狂)
现象:在 Class 中看到大量的基本型数据项目( Data Item ),如 Employee 类中有大量的数据成员, Employee#, FirstName, MiddleName, LastName, Address, State, City, Street, Zip, OfficePhone, CellPhone, Email…… 等等。
重构策略:使用 Extract Class (提炼新类)或 Preserve Whole Object (使用已存在的对象取代这些参数),实现使用对象代替基本型数据项目( Data Item )。如上述 Employee 类中就可分别提炼出 EmployeeName EmployeeContact 两个新类。
 
10 Switch Statements Switch 语句)
现象:同样的 Switch 语句出现在不同的方法或不同的 Class 中,这样当需要增加新的 CASE 分支或者修改 CASE 分支内语句时,就必须找到所有的地方,然后进行修改。这样,就比较麻烦了。
重构策略: (1) 首先采用 Extract Method Switch 语句提炼到一个独立的函数。
(2) 然后以 Move Method 搬移到需要多态性( Polymorphism )的 Superclass 里面或者是构建一个新的 Superclass
(3) 进一步使用 Replace Type Code with Subclasses 或者 Replace Type Code with State/Strategy 。这步就比较麻烦些,不过记住如下基本规则:这里一般有 3 Class 分别为 Source Class Superclass Subclass
Source Class
l         使用 Self Encapsulate Field ,将 Type Code 成员变量封装起来,也就是建立对应的 Setter/Getter 函数。
l         Source Class 中增加一个 Superclass 类型的成员变量,用来存放 Subclass 实例对象。
l         Source Class 中的 Getter 函数,通过调用 Superclass Abstract Query 函数来完成。
l         Source Class 中的 Setter 函数,通过调用 Superclass 中的 Static 工厂化方法来获取合适的 Subclass 实例对象。
 
Superclass
新建的一个 Class (注:就是上面通过 Move Method 搬移生成的 Superclass ),根据 Type Code 的用途命名该 Class ,作为 Superclass
l         Superclass 中建立一个 Abstract Query 函数,用来获取 Subclass Type Code
l         Superclass 中创建 Static 工厂化方法生产对应的 Subclass 对象,这里会存在一个 Switch 语句(不要再动脑筋来重构这个 Switch 语句了,这个 Switch 语句不会在多处重复存在,并且这里用于决定创建何种 Subclass 对象,这是完全可以接受的)。
 
Subclass
l         根据每一个 Switch/Type 分支,建立对应的 Subclass ,并且 Subclass 的命名可以参考 Switch/Type 分支的命名。
l         在每一个 Subclass 中重载 Superclass Abstract Query 函数,返回特定的 Type Code
(4) 现在 Superclass 仍然存在 Switch 分支,是时候轮到 Replace Conditional with Polymorphism 上场了。具体而言,就是在每一个 Subclass 中创建重载方法(注:该方法是 Superclass 中含有 Switch 语句的方法),并将 Superclass Switch 语句对应的 Case 分支剪切过来。最后将 Superclass 中该方法初象化 Abstract ,并清除 Switch 语句及其所有的 Case 分支。
这样就完成了整个重构过程,这个比较麻烦。
 
注:并不是一看到 Switch 语句及 CASE 分支,就马上 / 偏执狂采用上述重构策略进行重构,画蛇添足或吃亏不讨好(个人观点)。一般而言,只有看到多处出现相同的 Switch 语句时,才应该考虑进行重构。
 
11 Parallel Inheritance Hierarchies (平行继承体系)
现象:为某个 class 增加一个 subclass 时,也必须为另一个 class 相应增加一个 subclass 。重构策略: 在一个 class 继承体系的对象中引用( refer to )另一个 class 继承体系的对象,然后运用 Move Method Move Field 将被引用 class 中的一些方法和成员变量迁移宿主 class 中,消除被引用 class 的继承体系(注:这种平行继承体系好象比较少见也)。
 
12 Lazy Class (冗赘类)
现象:某一些 class 由于种种原因,现在已经不再承担足够责任,有些多余了。如同国有企业冗余人员一样,需要下岗了。
重构策略:通过 Collapse Hierarchy ,将这些冗余的 class 合并到 superclass subclass 中,或者通过 Inline Class (与 Extract Class 相反),将这些冗余 class 中的所有 Method/Field 迁移到其他相关的 class 中。
 
13 Speculative Generality (夸夸其谈未来性)
现象:系统中出现一些无用的 abstract class ,或者非必要的 delegation (委托),或者多余的参数等等。
重构策略:分别使用 Collapse Hierarchy 合并 abstract class ,使用 Inline Class 移除非必要的 delegation ,使用 Remove Parameter 删除多余的参数。
 
14 Temporary Field (令人迷惑的暂时值域)
现象: class 中存在一些 Field ,这些 Field 只在某种非常特定的情况下需要。
重构策略:通过 Extract Class 将这些孤独的 Field 及其相关的 Method 移植的一些新的 Class 中。提炼出来的新 Class 可能没有任何抽象意义,只是提供 Method 的调用,这些新 Class 一般称为 Method Object
 
15 Message Chains (过度耦合的消息链)
现象:向一个对象请求另一个对象,然后再向后者请求另一个对象,……,这就是 Message Chain ,意味着 Message Chain 中任何改变,将导致 Client 端不得不修改。
重构策略:通过 Hide Delegate (隐藏委托关系)消除 Message Chain ,具体做法是在 Message Chain 的任何地方通过 Extract Method 建立一个简单委托( Delegation )函数,来减少耦合( Coupling )。
 
16 Middle Man (中间转手人)
现象:过度运用 delegation ,某个 / 某些 Class 接口有一半的函数都委托给其他 class ,这样就是过度 delegation
重构策略:运用 Remove Middle Man ,移除简单的委托动作(也就是移除委托函数),让 client 直接调用 delegate 受托对象。和上面的 Hide Delegate (隐藏委托关系)刚好相反的过程。
 
由于系统在不断的变化和调整,因此 [ 合适的隐藏程度 ] 这个尺度也在相应的变化, Hide Delegate Remove Middle Man 重构策略可以系统适应这种变化。
 
另外,可保留一部分委托关系( delegation ),同时也让 Client 也直接使用 delegate 受托对象。
 
17 Inappropriate Intimacy (狎昵关系)
现象:两个 Class 过分亲密,彼此总是希望了解对方的 private 成分。
重构策略:可以采用 Move Method Move Field 来帮助他们划清界限,减少他们之间亲密行为。或者运用 Change Bidirectional Association to Unidirectional ,将双向关联改为单向,降低 Class 之间过多的依存性( inter-dependencies )。或者通过 Extract Class 将两个 Class 之间的共同点移植到一个新的 Class 中。
 
18 Alternative Classes with Different Interfaces (异曲同工的类)
现象:两个函数做相同的事情,却有不同的 signature
重构策略:使用 Rename Method ,根据他们的用途来重命名。另外,可以适当运用 Move Method 迁移某些行为,使 Classes 的接口保持一致。
 
19 Incomplete Library Class (不完美的程序库类)
现象: Library Class (类库)设计不是很完美,我们需要添加额外的方法。
重构策略:如果可以修改 Library Class Source Code ,直接修改最好。如果无法直接修改 Library Class ,并且只想修改 Library Class 内的一两个函数,可以采用 Introduce Foreign Method 策略:在 Client Class 中建立一个函数,以外加函数的方式来实现一项新功能(一般而言,以 server class 实例作为该函数的第一个参数)。
 
如果需要建立大量的额外函数,可应该采用 Introduce Local Extension :建立一个新 class ,使它包含额外函数,并且这个 class 或者继承或者 wrap (包装) source class
 
20 Data Class (纯稚的数据类)
现象: Data Class 指:一些 Class 拥有 Fields ,以及用来访问 Fields getter/setter 函数,但是没有其他的功能函数。(感觉这些 Data Class 如同 Entity Class Parameter Class ,用来传递参数,我认为这种情况下没有必要重构。)
重构策略:找出其他 class 中访问 Data Class 中的 getter/setter 的函数,尝试以 Move Method 将这些函数移植到 Data Class 中,实现将数据和操作行为(方法)包装在一起,也让 Data Class 承担一定的责任(方法)。
 
21 Refused Bequest (被拒绝的遗赠)
现象: Subclass 不想或不需要继承 superclass 的部分函数和 Field
重构策略:为 subclass 新建一个兄弟( sibling class ),再运用 Push Down Method Push Down Field superclass 中的相应函数和 Field 下推到兄弟 class ,这样 superclass 就只包含 subclass 共享的东西了。其实,也就是将 superclass 中一些与特定的函数和 Field 放到特定的 subclass 中, superclass 中仅包含 subclass 共享的函数和 Field
 
如果不想修改 superclass ,还可以运用 Replace Inheritance with Delegation 来达到目的。也就是以委托取代继承,在 subclass 中新建一个 Field 来保存 superclass 对象,去除 subclass superclass 的继承关系,委托或调用 superclass 的方法来完成目的。
 
22 Comments (过多的注释)
现象:(晕倒,这个也要重构, Remove 掉所有的 Comments 吗?不是。)当代码中出现一段长长的注释,一般是由于代码比较糟糕,需要进行重构,除去代码的坏味道。
重构策略:通过上面提及的各种重构策略,将代码的坏味道去除,使注释变成多余。
如果需要注释 / 解释一段代码做了什么,则可以试试 Extract Method ,提取出一个独立的函数,让函数名称解释该函数的用途 / 功能。另外,如果觉得需要注释来说明系统的某些假设条件,
也可尝试使用 Introduce Assertion (引入断言),来明确标明这些假设。
 
当你感觉需要撰写注释时,请先尝试重构,试着让所有的注释都变得多余。
 5 Divergent Change (发散式变化)

你可能感兴趣的:(refactor)