1.重构
代码写好之后改进它的设计-不改变代码的业务逻辑下,对代码的设计,结构改进,使代码的可读性增强,结构更加清晰 易读.
进行重构的时候,我们需要依赖测试,让它告诉我们是否引入了臭虫。好的测试是重构的根本
重构的节奏:测试、小修改、测试、小修改、…
2.重构的优点:
<1>使得代码尽量简单、优美、可扩展。
<2>能改进软件设计使软件更容易被理解
<3>能帮你找到bug
<4>提高软件的开发速度
3.重构"两顶帽子"
使用重构技术开发软件时,你把自己的时间分配给两种截然不同的行为:“添加新功能”和“重构”。
<1>添加新功能时,你不以你更改修改既有代码,只管添加新功能。通过测试,你可以衡量自己的工作进度。
<2>重构时你就不能再添加功能,只管改进程序结构。此时你不应该添加任何测试(除非发现先前遗漏的任何东西),
只在绝对必要时(用以处理接口变化)才修改测试。
4.什么时候进行重构
三次法则:事不过三,三则重构.
意思是说,一件事情,第一次只管去做,第二次做类似的事情会产生反感,但无论如何还是做了,
第三次再做类似的事情,你就应该重构.
<1>在添加新功能时进行重构.
<2>在修改bug时进行重构
<3>在代码复审时进行重构.
5.什么时候不进行重构
<1>现有的程序无法运行,此时应该是重写程序,而不是重构程序
<2>到了最后的交付期限,也应该避免重构
6.重构的难题
<1>关系数据库与面向对象编程的问题
在对象模型和数据库模型之间插入一个分隔层,这就可以隔离两个模型各自的变化.
升级某一模型时无需同时升级上述的分隔层即可.这样的分隔层会增加系统复杂度.
但是能增加灵活度.
<2>修改接口的问题
修改已发布的接口,因为已发布的接口会供外部人员(其它公司)使用,因此, 修改接口会导致
引用接口的其它程序不修改程序就无法运行.修改接口的最好的办法是增加一个新的接口,
让旧接口调用新接口.这样原来的程序就不用修改了.对于接口的另一个建议是心尽量不要发布接口.
7.需要重构的 "臭味条款"
1) Duplicated Code(重复代码)
2) Long Method(过长函数)
3) Large Class(过大类)
4) Long Parameter List(过长参数列)
5) Divergent Change(发散式变化)——过耦合
6) Shotgun Surgery(散弹式修改)——过度的分散与过耦合相反
7) Feature Envy(依恋情节)——函数对某个class的兴趣高过对自己所处的host class的兴趣。
症状:某个函数为了计算某值,从另一个对象那调用机会半打的取值函数。疗法:把这个函数移动到另一个地点。
8) Data Clumps(数据泥团)——症状:总是绑在一起出现的数据,很多地方可以看到相同的三或四笔数据项:
两个classes内的相同值域、许多函数签名式中的相同参数。疗法:把这些数据放进属于它们自己的对象中。
9) Primitive Obsession(基本型别偏执)——两种数据:结构型别和基本型别。充分利用“对象”的价值,
它模糊了横亘于基本数据和体积较大的classes之间界限。例如以class表示字符串和日期。
10) Switch Statements(switch惊悚现身)——面向对象程序的一个最明显特征就是:少用switch语句。
多态的概念可以为此带来优雅的解决办法。
11) Parallel Inheritance Hierarchies(平行继承体系)——每当为某个class增加一个subclass,
必须也为另一个class相应增加一个subclass。
12) Lazy Class(冗赘类)——如果一个class的所得不值其身价,他就应该消失。
13) Speculative Generality(夸夸其谈未来性)——以为“总有一天需要做这事”并因而企图以各式各样的挂钩
和特殊情况来处理一些非必要的事情。
14) Temporary Field(令人迷惑的暂时值域)——对象内某个instance变量仅为某种特定情势而设。
15) Message Chains(过度耦合的消息链)——用户向一个对象所求另一个对象,然后再向后者所求另一个对象,
然后……这就是消息链。
16) Middle Man(中间转手人)——某个class接口有一般的函数都委托给其他class,这样就是过度运用。
17) Inappropriate Intimacy(狎昵关系)——两个类之间过分亲密
18) Alternative Classes with Different Interfaces(异曲同工的类)——两个函数做同样的事情,
却有着不同的签名式。
19) Incomplete Library Class(不完美的程序库类)
20) Data Class(纯稚的数据类)——只含有一些值域以及访问值域的方法
21) Refused Bequest(被拒绝的遗赠)——子类应该集成弗雷的函数和数据。但如果他们不想或不需要继承。
22) Comments(过多的注释)
8.通过重构解决"臭味条款"
1.尽量消除重复的代码,将它们合而为一
<1>根据重复的代码出现在不同的地方,分别采取不同的重构的策略:
<2>在同一个Class的不同地方:通过采用重构工具提供的Extract Method功能提炼出重复的代码,
然后在这些地方调用上述提炼出方法。
<3>在不同Subclasses中:通过Extract Method提炼出重复的代码,然后通过Pull Up Method将该方法
移动到上级的Super class内。
<4>在没有关系的Classes中:通过对其中一个使用Extract Class将重复的代码提炼到一个新类中,
然后在另一个Class中调用生成的新类,消除重复的代码。
2.拆解过长的函数
过长的函数在我们的日常代码中经常可见,在C#中常通过#region #endregion区隔为不同的功能区域。
重构策略:通过Extract Method将过长的函数按照功能的不同进行适当拆解为小的函数,
并且给这些小函数一个好名字。通过名字来了解函数提供的功能,提高代码的理解性。
3.拆解过大的类
过大的类也经常见到,特别是类中含有大量的成员变量。
重构策略:通过Extract Class将一些相关成员变量移植到新的Class中,如Employee类,一般会包含有
联系方式的相关属性(电话, Mobile,地址,Zip等等),则可以将这些移植到新的EmployeeContact类中。
4.过长的参数列
过长的参数列的主要问题是难以理解,并且难以维护。如果要增加新的参数或者删除某一参数,易造成参数前后不一致。
重构策略:
<1>如果可以通过向已存在的对象查询获取参数,则可通过Replace Parameter with Method,移除参数列,
通过在函数内部向上述已存在的对象查询来获取参数。
<2>如果参数列中若干参数是已存在对象的属性,则可通过Preserve Whole Object将这些参赛替换为一个完整对象,
这样不仅提高代码的可读性,同时已易于代码今后的维护。
<3>还可以将若干不相关的参数,使用Introduce Parameter Object来创建一个新的参数类。
不过,如果这些情况过多的话,会产生很多莫名其妙的参数类了,反而降低代码的可读性。
5.Divergent Change(发散式变化)
现象:当某个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。
注:并不是一看到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。
重构策略:
<1>运用Remove Middle Man,移除简单的委托动作(也就是移除委托函数),让client直接调用delegate
受托对象。和上面的Hide Delegate(隐藏委托关系)刚好相反的过程。
<2>由于系统在不断的变化和调整,因此[合适的隐藏程度]这个尺度也在相应的变化,Hide Delegate和
Remove Middle Man重构策略可以系统适应这种变化。
<3>另外,可保留一部分委托关系(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(类库)设计不是很完美,我们需要添加额外的方法。
重构策略:
<1>如果可以修改Library Class的Source Code,直接修改最好。如果无法直接修改Library Class,
并且只想修改Library Class内的一两个函数,可以采用Introduce Foreign Method策略:
在Client Class中建立一个函数,以外加函数的方式来实现一项新功能(一般而言,
以server class实例作为该函数的第一个参数)。
<2>如果需要建立大量的额外函数,可应该采用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。
重构策略:
<1>为subclass新建一个兄弟(sibling class),再运用Push Down Method和Push Down Field将
superclass中的相应函数和Field下推到兄弟class,这样superclass就只包含subclass共享的东西了。
其实,也就是将superclass中一些与特定的函数和Field放到特定的subclass中,superclass中仅包含
subclass共享的函数和Field。
<2>如果不想修改superclass,还可以运用Replace Inheritance with Delegation来达到目的。
也就是以委托取代继承,在subclass中新建一个Field来保存superclass对象,去除subclass对
superclass的继承关系,委托或调用superclass的方法来完成目的。
22.Comments(过多的注释)
现象:当代码中出现一段长长的注释,一般是由于代码比较糟糕,需要进行重构,除去代码的坏味道。
重构策略:
<1>通过上面提及的各种重构策略,将代码的坏味道去除,使注释变成多余。
<2>如果需要注释/解释一段代码做了什么,则可以试试Extract Method,提取出一个独立的函数,
让函数名称解释该函数的用途/功能。另外,如果觉得需要注释来说明系统的某些假设条件,
也可尝试使用Introduce Assertion(引入断言),来明确标明这些假设。
<3>当你感觉需要撰写注释时,请先尝试重构,试着让所有的注释都变得多余。
9.注意:
1、如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便地那么做,那就先重构那个程序,
使特性的添加比较容易进行,然后再添加特性。
2、重构前,先检查自己是否有一套可靠的测试机制。这些测试必须有自我检验能力
3、重构技术系以微小的步伐修改程序。如果你犯下错误,很容易便可以发现它。
4、任何一个新手都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。
5、重构(名次):对软件内部结构的一种调整,目的是在不改变软件之可察行为前提下,
提高其可理解性,降低其修改成本。
6、重构(动词):使用一系列重构准则(手法),在不改变软件之可察行为前提下,调整其结构。
7、事不过三,三则重构。
8、不要过早发布接口。请修改你的代码拥有权政策,使重构更顺畅。
9、当你感觉需要撰写注释,请先尝试重构,试着让所有注释都变得多余。
10、确保所有测试都完全自动化,让它们检查自己的测试结果。
11、一整组测试就是一个强大的臭虫侦测器,能够大大缩减查找臭虫所需要的时间。
12、频繁地运行测试。每次编译请把测试也考虑进去,每天至少执行每个测试一次。
13、每当你接获臭虫提报,请先撰写一个单元测试来揭发这只臭虫。
14、编写未臻完善的测试并实际运行,好过对完美测试的无尽等待。
15、考虑可能出错的边界条件,把测试活力集中在那。
16、当事情被大家认为应该会出错时,别忘了检查此时是否有异常被如期抛出。
17、不要因为测试无法捕捉所有臭虫,就不撰写测试代码,因为测试的确可以捕捉到大多数臭虫。
代码写好之后改进它的设计-不改变代码的业务逻辑下,对代码的设计,结构改进,使代码的可读性增强,结构更加清晰 易读.
进行重构的时候,我们需要依赖测试,让它告诉我们是否引入了臭虫。好的测试是重构的根本
重构的节奏:测试、小修改、测试、小修改、…
2.重构的优点:
<1>使得代码尽量简单、优美、可扩展。
<2>能改进软件设计使软件更容易被理解
<3>能帮你找到bug
<4>提高软件的开发速度
3.重构"两顶帽子"
使用重构技术开发软件时,你把自己的时间分配给两种截然不同的行为:“添加新功能”和“重构”。
<1>添加新功能时,你不以你更改修改既有代码,只管添加新功能。通过测试,你可以衡量自己的工作进度。
<2>重构时你就不能再添加功能,只管改进程序结构。此时你不应该添加任何测试(除非发现先前遗漏的任何东西),
只在绝对必要时(用以处理接口变化)才修改测试。
4.什么时候进行重构
三次法则:事不过三,三则重构.
意思是说,一件事情,第一次只管去做,第二次做类似的事情会产生反感,但无论如何还是做了,
第三次再做类似的事情,你就应该重构.
<1>在添加新功能时进行重构.
<2>在修改bug时进行重构
<3>在代码复审时进行重构.
5.什么时候不进行重构
<1>现有的程序无法运行,此时应该是重写程序,而不是重构程序
<2>到了最后的交付期限,也应该避免重构
6.重构的难题
<1>关系数据库与面向对象编程的问题
在对象模型和数据库模型之间插入一个分隔层,这就可以隔离两个模型各自的变化.
升级某一模型时无需同时升级上述的分隔层即可.这样的分隔层会增加系统复杂度.
但是能增加灵活度.
<2>修改接口的问题
修改已发布的接口,因为已发布的接口会供外部人员(其它公司)使用,因此, 修改接口会导致
引用接口的其它程序不修改程序就无法运行.修改接口的最好的办法是增加一个新的接口,
让旧接口调用新接口.这样原来的程序就不用修改了.对于接口的另一个建议是心尽量不要发布接口.
7.需要重构的 "臭味条款"
1) Duplicated Code(重复代码)
2) Long Method(过长函数)
3) Large Class(过大类)
4) Long Parameter List(过长参数列)
5) Divergent Change(发散式变化)——过耦合
6) Shotgun Surgery(散弹式修改)——过度的分散与过耦合相反
7) Feature Envy(依恋情节)——函数对某个class的兴趣高过对自己所处的host class的兴趣。
症状:某个函数为了计算某值,从另一个对象那调用机会半打的取值函数。疗法:把这个函数移动到另一个地点。
8) Data Clumps(数据泥团)——症状:总是绑在一起出现的数据,很多地方可以看到相同的三或四笔数据项:
两个classes内的相同值域、许多函数签名式中的相同参数。疗法:把这些数据放进属于它们自己的对象中。
9) Primitive Obsession(基本型别偏执)——两种数据:结构型别和基本型别。充分利用“对象”的价值,
它模糊了横亘于基本数据和体积较大的classes之间界限。例如以class表示字符串和日期。
10) Switch Statements(switch惊悚现身)——面向对象程序的一个最明显特征就是:少用switch语句。
多态的概念可以为此带来优雅的解决办法。
11) Parallel Inheritance Hierarchies(平行继承体系)——每当为某个class增加一个subclass,
必须也为另一个class相应增加一个subclass。
12) Lazy Class(冗赘类)——如果一个class的所得不值其身价,他就应该消失。
13) Speculative Generality(夸夸其谈未来性)——以为“总有一天需要做这事”并因而企图以各式各样的挂钩
和特殊情况来处理一些非必要的事情。
14) Temporary Field(令人迷惑的暂时值域)——对象内某个instance变量仅为某种特定情势而设。
15) Message Chains(过度耦合的消息链)——用户向一个对象所求另一个对象,然后再向后者所求另一个对象,
然后……这就是消息链。
16) Middle Man(中间转手人)——某个class接口有一般的函数都委托给其他class,这样就是过度运用。
17) Inappropriate Intimacy(狎昵关系)——两个类之间过分亲密
18) Alternative Classes with Different Interfaces(异曲同工的类)——两个函数做同样的事情,
却有着不同的签名式。
19) Incomplete Library Class(不完美的程序库类)
20) Data Class(纯稚的数据类)——只含有一些值域以及访问值域的方法
21) Refused Bequest(被拒绝的遗赠)——子类应该集成弗雷的函数和数据。但如果他们不想或不需要继承。
22) Comments(过多的注释)
8.通过重构解决"臭味条款"
1.尽量消除重复的代码,将它们合而为一
<1>根据重复的代码出现在不同的地方,分别采取不同的重构的策略:
<2>在同一个Class的不同地方:通过采用重构工具提供的Extract Method功能提炼出重复的代码,
然后在这些地方调用上述提炼出方法。
<3>在不同Subclasses中:通过Extract Method提炼出重复的代码,然后通过Pull Up Method将该方法
移动到上级的Super class内。
<4>在没有关系的Classes中:通过对其中一个使用Extract Class将重复的代码提炼到一个新类中,
然后在另一个Class中调用生成的新类,消除重复的代码。
2.拆解过长的函数
过长的函数在我们的日常代码中经常可见,在C#中常通过#region #endregion区隔为不同的功能区域。
重构策略:通过Extract Method将过长的函数按照功能的不同进行适当拆解为小的函数,
并且给这些小函数一个好名字。通过名字来了解函数提供的功能,提高代码的理解性。
3.拆解过大的类
过大的类也经常见到,特别是类中含有大量的成员变量。
重构策略:通过Extract Class将一些相关成员变量移植到新的Class中,如Employee类,一般会包含有
联系方式的相关属性(电话, Mobile,地址,Zip等等),则可以将这些移植到新的EmployeeContact类中。
4.过长的参数列
过长的参数列的主要问题是难以理解,并且难以维护。如果要增加新的参数或者删除某一参数,易造成参数前后不一致。
重构策略:
<1>如果可以通过向已存在的对象查询获取参数,则可通过Replace Parameter with Method,移除参数列,
通过在函数内部向上述已存在的对象查询来获取参数。
<2>如果参数列中若干参数是已存在对象的属性,则可通过Preserve Whole Object将这些参赛替换为一个完整对象,
这样不仅提高代码的可读性,同时已易于代码今后的维护。
<3>还可以将若干不相关的参数,使用Introduce Parameter Object来创建一个新的参数类。
不过,如果这些情况过多的话,会产生很多莫名其妙的参数类了,反而降低代码的可读性。
5.Divergent Change(发散式变化)
现象:当某个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。
注:并不是一看到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。
重构策略:
<1>运用Remove Middle Man,移除简单的委托动作(也就是移除委托函数),让client直接调用delegate
受托对象。和上面的Hide Delegate(隐藏委托关系)刚好相反的过程。
<2>由于系统在不断的变化和调整,因此[合适的隐藏程度]这个尺度也在相应的变化,Hide Delegate和
Remove Middle Man重构策略可以系统适应这种变化。
<3>另外,可保留一部分委托关系(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(类库)设计不是很完美,我们需要添加额外的方法。
重构策略:
<1>如果可以修改Library Class的Source Code,直接修改最好。如果无法直接修改Library Class,
并且只想修改Library Class内的一两个函数,可以采用Introduce Foreign Method策略:
在Client Class中建立一个函数,以外加函数的方式来实现一项新功能(一般而言,
以server class实例作为该函数的第一个参数)。
<2>如果需要建立大量的额外函数,可应该采用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。
重构策略:
<1>为subclass新建一个兄弟(sibling class),再运用Push Down Method和Push Down Field将
superclass中的相应函数和Field下推到兄弟class,这样superclass就只包含subclass共享的东西了。
其实,也就是将superclass中一些与特定的函数和Field放到特定的subclass中,superclass中仅包含
subclass共享的函数和Field。
<2>如果不想修改superclass,还可以运用Replace Inheritance with Delegation来达到目的。
也就是以委托取代继承,在subclass中新建一个Field来保存superclass对象,去除subclass对
superclass的继承关系,委托或调用superclass的方法来完成目的。
22.Comments(过多的注释)
现象:当代码中出现一段长长的注释,一般是由于代码比较糟糕,需要进行重构,除去代码的坏味道。
重构策略:
<1>通过上面提及的各种重构策略,将代码的坏味道去除,使注释变成多余。
<2>如果需要注释/解释一段代码做了什么,则可以试试Extract Method,提取出一个独立的函数,
让函数名称解释该函数的用途/功能。另外,如果觉得需要注释来说明系统的某些假设条件,
也可尝试使用Introduce Assertion(引入断言),来明确标明这些假设。
<3>当你感觉需要撰写注释时,请先尝试重构,试着让所有的注释都变得多余。
9.注意:
1、如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便地那么做,那就先重构那个程序,
使特性的添加比较容易进行,然后再添加特性。
2、重构前,先检查自己是否有一套可靠的测试机制。这些测试必须有自我检验能力
3、重构技术系以微小的步伐修改程序。如果你犯下错误,很容易便可以发现它。
4、任何一个新手都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。
5、重构(名次):对软件内部结构的一种调整,目的是在不改变软件之可察行为前提下,
提高其可理解性,降低其修改成本。
6、重构(动词):使用一系列重构准则(手法),在不改变软件之可察行为前提下,调整其结构。
7、事不过三,三则重构。
8、不要过早发布接口。请修改你的代码拥有权政策,使重构更顺畅。
9、当你感觉需要撰写注释,请先尝试重构,试着让所有注释都变得多余。
10、确保所有测试都完全自动化,让它们检查自己的测试结果。
11、一整组测试就是一个强大的臭虫侦测器,能够大大缩减查找臭虫所需要的时间。
12、频繁地运行测试。每次编译请把测试也考虑进去,每天至少执行每个测试一次。
13、每当你接获臭虫提报,请先撰写一个单元测试来揭发这只臭虫。
14、编写未臻完善的测试并实际运行,好过对完美测试的无尽等待。
15、考虑可能出错的边界条件,把测试活力集中在那。
16、当事情被大家认为应该会出错时,别忘了检查此时是否有异常被如期抛出。
17、不要因为测试无法捕捉所有臭虫,就不撰写测试代码,因为测试的确可以捕捉到大多数臭虫。