#
你常常可以在很多地方看到相同的三或四笔数据项:两个classes内的相同值域(field)、许多函数签名式(signature)中的相同参数。这
些[总是绑在一起出现的数据]真应该放进属于它们自己的对象中。首先请找出这些数据的值域形式(field)出现点,运用Extract
Class(149)将它们提炼到一个独立对象中。然后将注意力转移到函数签名式(signature)上头,运用Introduce Parameter Object(295)或Preserve Whole Object(288)为它减肥。这么做的直接好处是可以将很多参数列缩短,简化函数调用动作。是的,不必因为Data Clumps只用上新对象的一部分值或而在意,只要你以新对象取代两个(或更多)值域,你就值回票价了。
一个好的评断办法是:删掉众多数据中的一笔。其他数据有没有因而失去意义?如果它们不再有意义,这就是个明确信号:你应该为它们产生一个新对象。
缩短值域个数和参数个数,当然可以去除一些坏味道,但更重要的是:一旦拥有新对象,你就有机会让程序散发出一种芳香。得到新对象后,你就可以着手寻找Feature Envy,这可以帮你指出[可移到新class]中的种种程序行为。不必太久,所有classes都将在它们的小小社会中充分发挥自己的生产力。
无数次经验里,我们看到某个函数为了计算某值,从另一个对象那儿调用几乎半打的取值函数(getting method)。疗法显而易见:把这个函数移至另一个地点。你应该使用Move Method(142)把它移到它该去的地方。有时侯函数中只有一部分受这种依恋之苦,这时候你应该使用Extract Method(110)把这一部分提炼到独立函数中,再使用Move Method(142)带它去它的梦中家园。
当然,并非所有情况都这么简单。一个函数往往会用上数个classes特性,那么它究竟该被置于何处呢?我们的原则是:判断哪个class拥有最多[被此函数使用]的数据,然后就把这个函数和那些数据摆在一起。如果先以Extract Method(110)将这个函数分解为数个较小函数并分别置放于不同地点,上述步骤也就比较容易完成了。
有数个复杂精巧的模式(patterns)破坏了这个规则。说起这个话题,[四巨头][Gang of Four]的Strategy和Visitor立刻跳入我的脑海,Kent Beck的Self Delegation[Beck]也在此列。使用这些模式是为了对抗坏味道Divergent Change。最根本的原则是:将总是一起变化的东西放在一块儿。[数据]和[引用这些数据]的行为总是一起变化的,但也有例外。如果例外出现,我们就搬移那些行为,保持[变化只在一地发生]。Strategy和Visitor使你得以轻松修改函数行为,因为它们将少量需被覆写(overridden)的行为隔离开来-当然也付出了[多一层间接性]的代价。
Shotgun Surgery和Divergent Change,但恰恰相反。如果每遇到某种变化,你都必须在许多不同的classes内做出许多小修改以响应之,你所面临的坏味道就是Shotgun Surgery。如果需要修改的代码散布四处,你不但很难找到它们,也很容易忘记某个重要的修改。
这种情况下你应该使用Move Method(142)和Move Field(146)把所有需要修改的代码放进同一个class。如果眼下没有合适的class可以安置这些代码,就创造一个。通常你可以运用Inline Class(154)把一系列相关行为放进同一个class。这可能会造成少量Divergent Change,但你可以轻易处理它。
Divergent Change是指[一个class受多种变化的影响],Shotgun Surgery则是指[一种变化引发多个class相应修改]。这两种情况下你都会希望整理代码,取得[外界变化]与[待改类]呈现一对一关系的理想境地。
如果某个class经常因为不同的方向上发生变化,Divergent
Change就出现了。当你看着一个class说[呃,如果新加入一个数据库,我必须修改这三个函数;如果新出现一种金融工具,我必须修改这四个函数],
那么此时也许将这个对象分成两个会更好,这么一来每个对象就可以只因一种变化而需要修改。当然,往往只有在加入新数据库或新金融工具后,你才能发现这一
点。针对某一外界变化的所有相应修改,都只应该发生在单一class中,而这个新class内的所有内容都应该外界变化。为此,你应该找出因着某特定原因
而造成的所有变化,然后运用Extrace Class(149)将它们提炼到另一个class中。
如果某个class经常因为不同的方向上发生变化,Divergent Change就出现了。
如果[向既有对象发出一条请求]就可以取得原本位于参数列上的一份数据,那么你应该激活重构准则Peplace Parameter with Method(292)。上述的既有对象可能是函数所属class内的一个值域(field),也可能是另一个参数。你还可以运用Preserve Whole Object(288)将来自同一对象的一堆数据收集起来,并以该对象替换它们。如果某些数据缺乏合理的对象归属,可使用Introduce Parameter Object(295)为它们制造出一个[参数对象]。
此间存在一个重要的例外。有时侯你明显不希望造成[被调用对象]与[较大对象]间的某种依存关系。这时候将数据从对象中拆解出来单独作为参数,也很合情合
理。但是请注意其所引发的代价。如果参数列太长或变化太频繁,你就需要重新考虑自己的依存结构(dependency structure)了。
如果想利用单一class做太多事情,其内往往就会出现太多instance变量。一旦如此,Duplicated Code也就是接踵而至了。
你可以运用Extract Class(149)将数个变量一起提炼
至新class内。提炼时应该选择class内彼此相关的变量,将它们放在一起。例如“depositAmount”和
“depositCurrency”可能应该隶属同一个class。通常如果class内的数个变量有着相同的前缀或字尾,这就意味有机会把它们提炼到某
个组件内。如果这个组件适合作为一个subclass,你会发现Extract Subclass(330)往往比较简单。
有时候class并非在所有时刻都使用所有instance变量。果真如此,你或许可以多次使用Extract Class(149)或Extract Subclass(330)。
和[太多instance变量]一样,class内如果有太多代码,也是[代码重复、混乱、死亡]的绝佳滋生地点。最简单的解决方案(还记得吗,我们喜欢
简单的解决)是把赘余的东西消弭于class内部。如果有五个[百行函数],它们之中很多代码都相同,那么或许你可以把它们变成五个[十行函数]和十个提
炼出来的[双行函数]。
和[拥有太多instance变量]一样,一个class如果拥有太多代码,往往也适合使用Extract Class(149)和Extract Subclass(330)。这里有个有用技巧:先确定客户端如何使用它们,然后运用Extract Interface(341)为每一种使用方法提炼出一个接口。这或许可以帮助你看清楚如何分解这个class。
如果你的Large Class是个GUI class,你可能需要把数据和行为移到一个独立的领域对象(domain object)去。你可能需要两边各保留一些重复数据,并令这些数据同步(sync.)。Duplicated Observed Data(189)告诉你该怎么做。这种情况下,特别是如果你使用旧式Abstract Windows Toolkit(AWT)组件,你可以采用这种方式去掉GUI class并代以Swing组件。
最终的效果是:你应该更积极进取地分解函数。我们遵循这样一条原则:每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,
并以其用途(而非实现手法)命名。我们可以对一组或甚至短短一行代码做这件事。哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,我
们也该毫不犹豫地那么做。关键不在于函数长度,而在于函数[做什么]和[如何做]之间的语义距离。
百分之九十九的场合里,要把函数变小,只需使用Extract Method(110)。找到函数中适合集在一起的部分,将它们提炼出来形成一个新函数。
如果函数内有大量的参数和临时变量,它们会对你的函数提炼形成阻碍。如果你尝试运用Extract Method(110),最终就会把许多这些参数和临时变量当作参数,传递给被提炼出来的新函数,导致可读性几乎没有任何提升。啊是的,你可以经常运用Replace Temp with Query(120)来消除这些暂时元素。Introduce Parameter Object(295)和Preserve Whole Object(288)则可以将过长的参数列变得更简洁一些。
如果你已经这么做了,仍然有太多临时变量和参数,那就应该使出我们的杀手锏:Replace Method with Method Object(135)。
如何确定该提炼哪一段代码呢?一个很好的技巧是:寻找注解。它们通常是指出[代码用途和实现手法间的语义距离]的信号。如果代码前方有一行注解,就是在提
醒你:可以将这段代码替换成一个函数,而且可以在注解的基础上给这个函数命名。就算只有一行代码,如果它需要以注解来说明,那也值得将它提炼到独立函数
去。
条件式和循环常常也是提炼的信号。你可以使用Decompose Conditional(238)处理条件式。至于循环,你应该将循环和其内的代码提炼到一个独立函数中。
在$HOME/.fcitx下修改config文件的:
LumaQQ支持=1
上面改为1,重启X就好了。
最单纯的Duplicated Code就是[同一个class内的两个函数含有相同表达式(express)]。这时候你需要做的就是采用Extract Method(110)提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。
另一种常见情况就是[两个互为兄弟(sibling)的subclass内含相同表达式]。要避免这种情况,只需对两个classes都使用Extract Method(110),然后再对被提炼出来的代码使用Pull Up Method(332),将它推入superclass内。如果代码之间是类似,并非完全相同,那么就得运用Extract Method(110)将相似部分和差异部分割开,构成单独一个函数。然后你可能发现或许可以运用Form Template Method(345)获得一个Template Method设计模式。如果有些函数以不同的算法做相同的事,你可以择定其中较清晰的一个,并使用Substitute Algorithm(139)将其他函数的算法替换掉。
如果两个毫不相关的classes内出现Duplicated Code,你应该考虑对其中一个使用Extract Class(149),
将重复代码提炼到一个独立class中,然后在另一个class内使用这个新class。但是,重复代码所在的函数也可能的确只应该属于某个class,
另一个class只能调用它,抑或这个函数可能属于第三个class,而另两个classes应该引用这第三个class。你必须决定这个函数放在哪个最
合适,并确保它被安置后就不会再在其他任何地方出现。
|