重构—改善既有代码的设计003:代码的坏味道(Bad smells in Code)
一:重复的代码(Duplicated Code)
二:过长函数(LONG METHOD)
三:过大类(LARGE CLASS)
四:过长参数列(LONG PARAMETER LIST)
五:发散式变化(DIVERGENT CHANGE)
六:散弹式修改(SHOTGUN SURGERY)
七:依恋情结(FEATURE ENVY)
八:数据泥团(DATA CLUMPS)
九:基本型别偏执(PRIMITIVE OBSESSION)
十:SWITCH STATEMENTS
十一:平行集成体系(PARALLEL INHERITANCE HIERARCHIES)
十二:冗赘类(LAZY CLASS)
十三:夸夸其谈的未来型(SPECULATIVE GENERALITY)
十四:令人迷惑的暂时值域(TEMPORARY FIELD)
十五:过度耦合的消息链(MESSAGE CHAINS)
十六:中间转手人(MIDDLE MAN)
十七:不恰当的亲密关系(INAPPROPRIATE INTIMACY)
十八:异曲同工的类(ALTERNATIVE CLASSES WITH DIFFERENT INTERFACES)
十九:不完美的程序库类(INCOMPLETE LIBRARY CLASS)
二十:纯稚的数据类(DATA CLASS)
二一:被拒绝的遗赠(REFUSED BEQUEST)
二二:过多的注释(COMMENTS)
一:重复的代码(Duplicated Code)
如果在一个以上的地点看到相同的程序结构,那么当可肯定:设法将它们合而为一,程序会变得更好。
1:同一个CLASS内两个函数含有相同表达式EXTRACT METHOD
提炼重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。
2:两个互为兄弟的子类含有相同表达式EXTRACT METHOD + PULL UP METHOD
对两个类都提炼重复代码,将提炼出来的代码放入超类中。
3:如果两个毫不相关的类EXTRACT CLASS
将重复代码提炼到一个独立的CLASS中,然后再另一个CLASS中使用这个新CLASS
二:过长函数(LONG METHOD)
每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立的函数中,并以其用途命名。
百分之九十九的场合,要把函数变小,只需要使用EXTRACT METHOD,找到函数中适合集中在一起的部分,将它们提炼出来形成一个新的函数。
1:如果函数内有大量的参数和临时变量,它们会对你的函数提炼形成阻碍。
如果使用EXTRACT METHOD方法,最终会把许多参数和临时变量当做参数,传递给被提炼出来的新函数,导致可读性几乎没有任何提升。
可以用REPLACE TEMP WITH QUERY来消除这些暂时的元素
INTRODUCE PARAMETER OBJECT 和 PRESERVE WHOLE OBJECT则可以将过长的参数列表变得简洁一些。
2:如果这是仍有太多的临时变量和参数,则可以考虑REPLACE METHOD WITH METHOD OBJECT
三:过大类(LARGE CLASS)
1:如果单一CLASS做太多的事情,其内往往就会出现太多INSTANCE变量。
可以用EXTRACT METHOD将数个变量一起提炼致新CLASS中。提炼时应该选择CLASS内彼此相关的变量,将它们放在一起。
2:一个CLASS内如果有太多代码,也是代码重复,混乱,死亡的绝佳滋生地点。
四:过长参数列(LONG PARAMETER LIST)
太长的参数列难以理解,太多参数会造成前后不一致,不易使用,而且一旦你需要更多的数据,就不得不修改它。
可以通过传递对象类解决
REPLACE PAREMETER WITH METHOD + PRESERVE WHOLE OBJECT + INTRODUCE PARAMETER OBJECT
五:发散式变化(DIVERGENT CHANGE)
我们希望软件容易被修改,即一旦需要修改,希望能够调到某一点,只在该处修改。
即针对某一外界便所的所有相应修改,都只应该发生在单一CLASS中,而这个新CLASS内的所有内容都应该反映该外界的变化。
所以应该找出因着某些特定原因而造成的所有变化,然后应用EXTRACT CLASS将它们提炼到另一个CLASS中。
六:散弹式修改(SHOTGUN SURGERY)
1:如果遇到某种变化,你都必须在许多不同CLASS内作出许多小修改以相应之。
使用MOVE METHOD + MOVE FIELD把所有需要修改的代码放进同一个CLASS,如果没有适合的CLASS安置,就创造一个。
2:区别
发散式变化:一个CLASS受多种变化的影响
散弹式修改:一种变化引发多个CLASS相应修改
七:依恋情结(FEATURE ENVY)
对象:将数据和加诸其上的操作行为包装在一起
1:函数对某个CLASS的兴趣搞过对自己所处之HOST CLASS的兴趣,常见的是数据。
常看到某个函数为了计算某值,从一个对象那儿调用了几乎半打的取值函数。
使用MOVE MOTHOD + EXTRACT METHOD将其移到它该去的地方。
2:当一个函数用数个CLASS特性时,该放置何处?
判断哪个CLASS拥有最多被此函数调用的数据,然后就把这个函数和那些数据摆在一起。
八:数据泥团(DATA CLUMPS)
1:很多数据喜欢成群结队的待在一起,常常可以在很多地方看到相同的三或四笔数据项。
将这些一起出现的数据放在一起,提炼到一个独立对象中。
2:评判办法:删除众多数据中的一笔,其他数据没有因而失去意义?如果他们不再有意义,则说明该为它们产生一个新对象了。
九:基本型别偏执(PRIMITIVE OBSESSION)
应用REPLACE DATA VALUE WITH OBJECT将原本单独存在的数据值替换为对象,从而走出传统的洞窟,进入只收可热的对象世界。
十:SWITCH STATEMENTS
SWITCH语句的问题在于重复,常常发现同样的SWITCH语句三步于不同的地点,如果要为它添加一个新CASE子句,则必须找到所有SWITCH语句并修改它们。
SWITCH可以考虑使用多态解决。
用EXTRACT METHOD将SWITCH提炼到一个独立的函数中,用MOVE METHOD将它搬移到需要多态的类中。
再用REPLACE TYPE CODE WITH SUBCLASS或REPLACE TYPE CODE WITH STATE/STRATEGY。
这样,一般完成集成结构之后,就可以运用REPLACE CONDITIONAL WITH POLYMORPHISM了。
如果只是在单一函数中有些选择事例,而又不想改动它们,那就没必要用多态了,REPLACE PARAMETER WITH EXPLICIT METHODS是个不错的选择。
十一:平行集成体系(PARALLEL INHERITANCE HIERARCHIES)
每当为某个CLASS增加一个子类SUBCLASS时,也必须为另一个类CLASS相应增加一个子类SUBCLASS。
十二:冗赘类(LAZY CLASS)
即如果一个类的所得不值得其身份,它就应该消失
1:当一个类重构后,自身缩水
2:开发者事前规划某些变化,并添加一个CLASS来应付这些变化,但变化实际上并没有发生
3:如果某些子类没有做足够的工作,用COLLAPSE HIERARCHY。
十三:夸夸其谈的未来型(SPECULATIVE GENERALITY)
用不上的装置只会档你的路,应搬开
1:如果没有抽象类ABSTRACT CLASS没有太大的作用,可以运用COLLAPSE HIERARCHY除掉
2:如果函数的某些参数未被用上,可用REMOVE PARAMETER去除
3:如果函数名称带有多于的抽象意味,可用RENAME METHOD去除
4:如果函数或CLASS得惟一用户是测试用例,则去除
十四:令人迷惑的暂时值域(TEMPORARY FIELD)
有些对象的某些变量仅为某种特定情势而设,这样的代码让人不易理解。因为通常认为对象在所有时候都需要它的所有变量。
十五:过度耦合的消息链(MESSAGE CHAINS)
观察MESSAGE CHAIN最终得到的对象是用来干什么的,看看是否已EXTRACT METHOD把使用该对象的代码提炼到一个独立的函数中,再运用MOVE METHOD把这个函数推入MESSAGE CHAINS中。
十六:中间转手人(MIDDLE MAN)
1:当某个CLASS接口有一半的函数都委托给其他CLASS时,就存在过度应用了。这时应该直接和实际对象打交道。
2:如果这样不干实事的函数只有少数几个,可以运用INLINE METHOD把它们内联进调用端。
十七:不恰当的亲密关系(INAPPROPRIATE INTIMACY)
过分亲密的类必须拆散。可以使用MOVE METHOD 和 MOVE FIELD帮它们划清界面,从而减少亲密行为。
如果两个类是在情投意合,可以运用EXTRACT CLASS把两者共同点提炼到一个安全地点,让他们坦荡地使用这个新类。
十八:异曲同工的类(ALTERNATIVE CLASSES WITH DIFFERENT INTERFACES)
如果两个函数做同一件事情去,却有着不同的签名,需应用RENAME METHOD根据他们的用途重新命名。
十九:不完美的程序库类(INCOMPLETE LIBRARY CLASS)
库类的构建者没有未卜先知的能力。
麻烦的是类库的形式往往不够好,往往不可能让我们修改其中的类使它完成我们希望的工作。
如果想修改库类的一两个函数,可以应用INTRODUCE FOREIGN METHOD
如果想要添加一大堆额外行为,就得应用INTRODUCE LOCAL EXTENSION
二十:纯稚的数据类(DATA CLASS)
DATA CLASS:即拥有一些值域,以及用于访问这些值域的函数,除此之外一无长物。
这些类早期可能还有PUBLIC值域或容器,这是应该用ENCAPSULATE FIELD将它们封装起来。对于那些不该被其他类修改的值域,应用REMOVE SETTING METHOD
二一:被拒绝的遗赠(REFUSED BEQUEST)
子类应该继承父类的函数和数据,但如果它们不想或不需要继承,这时该如何?
传统做法,为这个子类新建一个兄弟,在运用PUSH DOWN MOETHOD+PUSH DOWN FIELD把所有用不到的函数下推给那个兄弟。
这样一来,父类就只持有所有子类共享的东西。
二二:过多的注释(COMMENTS)
当感觉需要注释时,请先尝试重构,试着让所有注释都变得多余。
有时看到一段长长的注释,而这些注释之所以存在是因为代码很糟糕。