本文继续《重构(Refactoring)技巧读书笔记éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
之一》,重构的确是未来软件工程师需要掌握的一项技能。目前一些支持.Net的重构工具,如ReSharper for VS.Net v1.0Borland Together for VS.Net v2.0VS.Net 2005等,只能支持一些有限的、比较简单的重构策略。大量的重构策略需要软件工程师清晰的了解,人工为主,运用重构工具辅助进行。(注:本文重构策略的名称及其大部分内容来自《重构-改善既有代码的设计》一书,Martin Fowler 著,侯捷等译)。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
下面的内容延续上一节的内容,其中提及的一些代码坏味道Bad Smell in Codes及其重构策略相对而言要比较麻烦一些。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
一、代码坏味道(Bad Smell in Codes)及其重构策略éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
5Divergent Change(发散式变化)éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
现象:当某个Class因为外部条件的变化或者客户提出新的功能要求等时,每次修改要求我们更新Class中不同的方法。不过这种情况只有在事后才能觉察到,因为修改都是在事后发生的么(废话)。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
重构策略:将每次因同一条件变化,而需要同时修改的若干方法通过Extract Class将它们提炼到一个新Class中。实现目标是:每次变化需要修改的方法都在单一的Class中,并且这个新的Class内所有的方法都应该与这个变化相关。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
6Shotgun Surgery(霰弹式修改)éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
现象:当外部条件发生变化时,每次需要修改多个Class来适应这些变化,影响到很多地方。就像霰弹一样,发散到多个地方。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
重构策略:使用Move MethodMove FieldClass中需要修改的方法及成员变量移植到同一个Class中。如果没有合适的Class,则创建一个新Class。实现目标是,将需要修改的地方集中到一个Class中进行处理。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
比较Divergent Change(发散式变化)和Shotgun Surgery(霰弹式修改):éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
前者指一个Class受到多种外部变化的影响。而后者指一种变化需要影响到多个Class需要修改。都是需要修理的对象。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
7Feature Envy(依恋情结)éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
现象:Class中某些方法“身在曹营心在汉”,没有安心使用Class中的成员变量,而需要大量访问另外Class中的成员变量。这样就违反了对象技术的基本定义:将数据和操作行为(方法)包装在一起。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
重构策略:使用Move Method将这些方法移动到对应的Class中,以化解其“相思之苦”,让其牵手。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
8Data Clumps(数据泥团)éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
现象:指一些相同数据项目(Data Items),如Class成员变量和方法中参数列表等,在多个Class中多次出现,并且这些数据项目有其内在的联系。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
重构策略:通过使用Introduce Parameter Object(创建新的参数对象取代这些参数)或Preserve Whole Object(使用已存在的对象取代这些参数),实现使用对象代替Class成员变量和方法中参数列表,清除数据泥团,使代码简洁,也提高维护性和易读性。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
9Primitive Obsession(基本型偏执狂)éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
现象:在Class中看到大量的基本型数据项目(Data Item),如Employee类中有大量的数据成员,Employee#, FirstName, MiddleName, LastName, Address, State, City, Street, Zip, OfficePhone, CellPhone, Email……等等。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
重构策略:使用Extract Class(提炼新类)或Preserve Whole Object(使用已存在的对象取代这些参数),实现使用对象代替基本型数据项目(Data Item)。如上述Employee类中就可分别提炼出EmployeeNameEmployeeContact两个新类。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
10Switch StatementsSwitch语句)éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
现象:同样的Switch语句出现在不同的方法或不同的Class中,这样当需要增加新的CASE分支或者修改CASE分支内语句时,就必须找到所有的地方,然后进行修改。这样,就比较麻烦了。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
重构策略:(1)首先采用Extract MethodSwitch语句提炼到一个独立的函数。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
(2)然后以Move Method搬移到需要多态性(Polymorphism)的Superclass里面或者是构建一个新的SuperclasséZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
(3)进一步使用Replace Type Code with Subclasses或者Replace Type Code with State/Strategy。这步就比较麻烦些,不过记住如下基本规则:这里一般有3Class分别为Source ClassSuperclassSubclasséZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
Source ClasséZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
léZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
使用Self Encapsulate Field,将Type Code成员变量封装起来,也就是建立对应的Setter/Getter函数。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
léZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
Source Class中增加一个Superclass类型的成员变量,用来存放Subclass实例对象。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
léZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
Source Class中的Getter函数,通过调用SuperclassAbstract Query函数来完成。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
léZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
Source Class中的Setter函数,通过调用Superclass中的Static工厂化方法来获取合适的Subclass实例对象。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
SuperclasséZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
新建的一个Class(注:就是上面通过Move Method搬移生成的Superclass),根据Type Code的用途命名该Class,作为SuperclasséZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
léZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
Superclass中建立一个Abstract Query函数,用来获取SubclassType CodeéZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
léZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
Superclass中创建Static工厂化方法生产对应的Subclass对象,这里会存在一个Switch语句(不要再动脑筋来重构这个Switch语句了,这个Switch语句不会在多处重复存在,并且这里用于决定创建何种Subclass对象,这是完全可以接受的)。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
SubclasséZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
léZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
根据每一个Switch/Type分支,建立对应的Subclass,并且Subclass的命名可以参考Switch/Type分支的命名。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
léZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
在每一个Subclass中重载SuperclassAbstract Query函数,返回特定的Type CodeéZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
(4)现在Superclass仍然存在Switch分支,是时候轮到Replace Conditional with Polymorphism上场了。具体而言,就是在每一个Subclass中创建重载方法(注:该方法是Superclass中含有Switch语句的方法),并将SuperclassSwitch语句对应的Case分支剪切过来。最后将Superclass中该方法初象化Abstract,并清除Switch语句及其所有的Case分支。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
这样就完成了整个重构过程,这个比较麻烦。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%
注:并不是一看到Switch语句及CASE分支,就马上/偏执狂采用上述重构策略进行重构,画蛇添足或吃亏不讨好(个人观点)。一般而言,只有看到多处出现相同的Switch语句时,才应该考虑进行重构。éZµžn), Åwww.netcsharp.cnt”ôX°¬ðÒ%