阅读 345

刀哥咱俩探讨一下重构的手法

重构三部曲:

  1. 刀哥我打算重构这块代码
  2. 来来竖三我跟你说说代码的坏味道
  3. 刀哥咱俩探讨一下重构的手法

周一早上,竖三早早的来到了空无一人的办公区,坐在工位上百无聊赖。心里还在想着周末的王者荣耀十五连胜,暗暗窃喜,我TMD太厉害,尔等且是凯爹的对手,加油干保持二十连胜。咦对了,刀哥今天要给我说说如何消除代码坏味道,我赶紧学习,等他来了跟他交流交流。说干就干,打开电脑就看了起来。

不知不觉一个小时过去了。竖三伸伸懒腰,感觉收获颇丰。正好上周六才上线新版本,今天无事,找刀哥聊聊去。

“刀哥,上周六你说今天给我讲讲消除代码坏味道的方法。今天什么时候有时间咱俩探讨探讨。” 竖三说

“就现在吧,版本刚发,闲来无事。”

“那我们就去休息区聊聊吧。”

“可以,走起。”

两人来到休息区坐下,开始讲些不知所云的话。

“上次说了代码的坏味道,其实基本都可以通过一些重构手法来消除。在《重构 改善既有代码的设计》这本书中写的非常详细。我这里只是概括性的说一下,后面你可以买一本来读读。” 刀哥说

“OK,我早上简单的看了一点。正好也梳理一下。” 竖三说

“OK,那我们开始。” 刀哥说着

重构手法非常多,可以根据书上的内容过一遍。下次遇到了可以在参考书上的操作做处理。我们按照坏味道来说吧。

重复代码

重复代码,分很多种情况,首先是如果是同一个类中出现重复代码,我们可以通过提炼函数(Extract Method)的方式来消除,这样出现重现代码的地方,现在调用同一个函数,这样就完美解决了。其次是两个类有相同重复代码,如果是兄弟俩,可以抽象到父类中来消除重复代码,比如使用模板方法(From Template Method)。如果没有关系,就直接提炼出一个类的方式(Extra Class)来消除或者放到Util中。

过长函数

函数是类的职责的体现,每个类基本上都离不开它。函数要尽量短小,一般情况下20行为宜,这样的函数可以更好的复用。函数过长的原因基本是语句不属于同一抽象层级,我们应该遵守自上而下的方式来组织函数。碰到过长函数,我们先审视一下函数的职责是否是干了太多事,用一个名字无法说明其功能就说明不合适,毁了重造,拆出多个函数。可以用一个名字说明其功能时候,那就说明函数体里面的语句不属于同一个抽象级别,我们可以提炼出函数(Extra Method),这样原来的函数里面的语句仅仅是调用函数,从而让整个函数结构清新起来。

过大的类

类臃肿基本上是职责过多,违反了单一职责。对于过大的类,我们一般是通过拆分出多个类的方式来消除,也就是提炼类(Extra Class)。对于职责有相关性的类,我们可以通过抽象出父类和子类的方式,对于没有相关性的类,我们直接抽象出类。

过长的参数列表

一般函数的参数列表以两个为宜,三个还可以接受,超过三个就是噩梦了,因为每次调用前需要搞懂参数的意思。对于参数列表过长可以通过以下方式消除:

  1. 把部分参数提取到所属的类中,这样可以减少参数,但是要注意字段是否真正属于这个类。

  2. 以函数取代参数。在函数体中通过调用函数的方式获取所需的信息,而不必通过参数。

  3. 传递整个对象。通常我们可能只传递对象的部分信息作为函数的参数,但是这么做会导致参数过多,索性就传递整个对象,这样就避免了参数过长。

  4. 引入参数对象。某些参数总是很自然地同时出现,那我们以一个对象来取代参数,也可以避免参数过长。

发散式变化

某个类经常因为不同的原因在不同的方向上发生变化,违反了单一职责。遇到这样的情况,基于变化提炼出相关的类(Extra Class),新类就应对了此变化。

霰弹式修改

一处变化引发多个类修改,杀伤力极大,代码难以维护。应对这种情况,就是运用移动函数,把修改移动到同一个类中,这样以后修改了只需要修改一个类,从而提高了可维护性。

依恋情结

函数对某个类的依赖关系高过自己所处类的兴趣,而我们应该把职责分配给具有完成职责所需信息的类,所以这种情况下就需要移动函数(Move Method),把它带到梦想家园。

数据泥团

可能看到多个类中有相同的字段,而且有许多函数传递相同字段作为参数,导致代码到处都是泥点。我们可以提炼类(Extra Class),让他们在类中玩耍,然后用传递整个对象(Preserve Whole Object),或者引入对象(Introduce Parameter Object)来为函数减肥。

Switch惊悚现身

Switch一出现基本是根据类型做相应的处理,相同的Switch语句处理散落在各处。我们一般可以应用多态来消除。首先我们把Switch语句提炼出函数(Extra Method),其次用子类代替类型码(Replace Type Code with Subclasses),亦或者用状态模式/策略模式(Replace Conditional with State/Strategy)代替类型码,从而封装提炼的函数,最后我们就可以用多态代替Switch语句了(使用多态取代条件语句(Replace Conditional with Polymorphism))。

平行继承体系

一个类继承体系的子类增加,导致另一个继承体系类也要加子类。说明类不是依赖抽象,而是依赖实现。我们可以让一个继承体系的实例引用另个继承体系的抽象类,然后通过移动函数(Move Method)和 移动字段(Move Field)把功能分配好,基本就可以消除此问题了。

夸夸其谈未来性

这种坏代码往往是识别的变化有问题,以为后面有变化,稳稳地设计了应对变化的代码,到头来不会有此种变化,这段代码也就废了。抽象类没有太大的作用,我们可以把父类和子类合为一体(Collapse Hierarchy)。不必要的类可以内联到其他的类(Inline Class)。函数的参数没有使用就删除。函数过于抽象就重命名。从而消除此类坏代码。

令人迷惑的暂时字段

类中的某个字段只为特定情况而设,这会降低类的内聚性,降低代码的可读性。我们可以通过提炼类(Extra Class)给这个字段安个家。

过度耦合的信息链

一般以XXobject.XX.XXX.XXX这种格式出现,就说明有过度耦合的信息链,此类代码违法了迪米特法则(最少知识原则),一旦接口变更,修改就大面积出现,降低了可维护性,同时也降低了代码的可读性。我们可以使用隐藏委托关系(Hide Delegate)把返回的对象功能给封装一下,避免对外暴露类内部引用的对象。

中间人

对象的特征之一就是封装,而封装往往伴随着委托。一半以上函数都是委托,就说明出现了过度委托,我们可以通过移除中间人(Remove Middle Man)的方式处理。

狎昵关系

类之间相互试探彼此的private成分,破坏了类的封装性,我们可以有如下的方式: 最简单的是通过移动方法(Move Method)和移动字段(Move Field)。 如果两个类之间是双向关联关系,我们可以通过将双向关联改为单向关联(Change Bidirectional Asscociation to Unidirectional),斩断一个类对另一个类的情愫。 如果两个类实在是难解难分,我们可以运用提炼类(Extra Class)把二者的关系提到安全的地方。或者运用隐藏委托(Hide Delegate)让另个一个委托类来承载他们交往的信息。 继承会造成过度亲密,如果子类只使用了父类的一部分接口,根本不需要继承就可以完成时候,我们可以通过委托取代继承(Replace Inheritance with Delegation)消除狎昵关系。

异曲同工的类

两个类中的两个函数却做了同一个事,会导致很难维护,这也是属于重复代码的一种。我们通过移动函数(Move Method)来把两个函数合为一个消除坏味道,如果涉及的字段,函数过多,也可以通过提炼类(Extra Class)来消除坏味道。

不完美的库类

有时引入的库中的类设计不够好,但我们又不能修改类来使其能完成我们期望的工作。为了解决此问题,其一我们可以使用引入外加函数(Introduce Foreign Method)方式,也就是在客户端的类中建立一个函数,并以参数传递库类。其二我们可以使用引入本地扩展(Introduce Local Extension)方式,也就是继承库类,并添加额外的函数。

纯稚的数据类

这样的类只包含读写函数,除此之外一无是处。对于类我们期望隐藏其实现,而隐藏实现并不是在字段和访问之间添加一层函数,隐藏实现关乎抽象,类并不是简单地暴露取值函数和赋值函数从而让类变量供外界使用,而是暴露接口,以便外界无需了解实现就能操作变量。修复这样的问题,就是找到调用点,看看能不能通过移动函数(Move Method)把调用行为移动到数据类中。

被拒绝的遗赠

子类应该继承父类的字段以及函数,但是子类不想继承父类的部分字段或者函数就出来了此问题,这说明父类的一些接口不是所有的子类都需要,可以新建兄弟类,运用函数下移(Push Down Method),字段下移(Push Down Field)把函数和字段移动到兄弟类中,保证正确的继承关系。

过多的注释

注释更容易让代码理解,但给可读性差的代码,无法自释的代码写注释就有问题了,见到这类问题及早修改。运用提炼函数(Extra Method)或者重命名(Rename Method)让代码可以自释。

“至此,我基本说完了,但是还有很多重构手法没有讲到。” 刀哥说。

“这有点多呀。” 竖三说。

“没事,等你遇到了不懂的再问我或者看书。” 刀哥说。

“那行。” 竖三说。 “刚给你讲了重构,趁热打铁,你可以重构上次说的支付功能的代码了。” 刀哥说。 “好的,刀哥你要帮忙把把关呀。” 竖三两眼放光看着刀哥说。 “好的。看好你呀。” 刀哥拍了拍竖三的肩膀说着。

竖三一边微笑一边点头。

说吧。两人站起身回到工位工作去了。

参考阅读:

  • 《重构:改善既有代码的设计》
  • 《代码整洁之道》

限于个人水平,有错误请指出,大家共同学习进步!

扫码关注公众号,查看更多内容。