重构之路之一

本文总结收纳一些重构思维,鉴于项目代码中,还是懒的缘故,功能注重实现。
不注重维护成本,问题较多,牵一发动全身的代码较多。重构任务,应该循循渐进。
这里收集,学习一些重构相关的知识点。以前一直缺乏这方面的学习,该恶补了。


重构的意义

重构可以让我们的代码条理清晰且简明易读。重构需要借用OO原则,来加强扩展性及灵活性。

总之一句话,磨刀不误砍柴工。重构的意义在于,今天写的代码,明天及未来,能避免多次推翻重来。

如何重构

首先需要知道的是,重构的目的在于:不改变功能的情况下,提高可理解性,降低修改维护代码的成本。

重构原则:

  • 一次只修改一处地方,然后进行测试,保证没修改出新问题。
  • 如果要新添加一个功能,程序无法很方便达成目的,那么就先重构那个程序。(不要说下次)

重构重点

过长方法(Long Method)

重复代码(Duplicated Code)

  • 问题:针对重复代码,如果你要修改这些地方中的一个。很可能导致:遗漏、工作量大;耗时耗力。

  • 解决方案:

    • 遵循DRY(Don’t Repeat Yourself,不要重复你自己)
  • 示例:重构】-重复代码(duplicated Code)

过大类(Large Class)

  • 问题:该类承担可过多的责任,如果某个责任需要修改,你就要修改这个巨大的耦合类,带来维护成本加高。

  • 解决方案:

    • 可使用Extract Class,Move Method等手段来帮他瘦身。
    • 遵循SRP(Single Responsibility Principle)单一责任原则
    • 示例:一个Activit中包含了网络请求,数据转换,显示UI等等。(mvvm,mvp等模式实现)

过长参数类(Long Parameter List)

  • 问题:调用者需要找到每一个适合的参数,才可以调用他。1.不方便。 2.修改方法时容易举步维艰

  • 解决方案:

    • 可用Introduce Parameter Object(引入参数对象),Method Object(函数对象; 方法对象),Replace Parameter With Method(以函数对象取代函数)等重构手法
  • 示例:大话重构 之 消除过长方法

发散式变化(Divergent Change)

  • 问题:如果某个类由需求改变需要换一种实现方式,但是总体意图不变。此时,如果你的代码过于耦合,以至于十分困难的进行修改,那么,你的代码就发出了发散式变化的凑味道。

  • 解决方案:

    • 你应该将可能变化的地方提取到一个Class,以应变这种未知的变化。
  • 示例:『如果新加入一个数据库,我必须修改这三个函数;如果新出现一种金融工具,我必须修改这四个函数』那么最好将这几个函数单独封装成一个类

霰弹式修改(Shotgun Surgery)

  • 问题:用词语形容说:牵一发动全身 (如某一个小的修改需要在程序的许多其他地方修改,那么你的代码飘着霰弹式修改的臭味。)

  • 解决方案:

    • 你需要Move Method,或重构手段将这些可能修改的地方移动到统一地方。
  • 示例:如果需要修改的代码散布四处,你不但很难找到它们,也很容易忘记某个重要的修改。

依恋情节 (Feature Env)

  • 问题:如果存在 class A 和 class B,当调用了中A中的方法却使用了过多B中的数据,那么A对B 就存在过多的依赖
  • 解决方案:
    • 如果class A中存在B占用类最深,则将A中的方法移动到B中
    • 如果class A中还存在着比B占用类深的class,则将A中的方法移动到占有类最深的class中

数据泥团 (Data Clumps)

  • 问题:如果多个(三个以上)对象存在,经常出现在不同class的值域中;导致重复代码过多
  • 解决方案:
    • 如果去掉其中一个就没有意思,那么你应该将他们使用Extract Class为他们安个家。(判断方法:如果去除他们中一个,他们就没有多大意义,那么不要犹豫,他们就应该在一个类中)

基本类型偏执 (Primitive Obsession)

  • 问题:针对基本类型数据 (int float等等),比如存储的电话号码,如果需要区号,那么在宿主类中就需要再提供方法,如果还要获取隐藏的手机号码呢。宿主类将变得杂而乱了。
  • 解决方案:
    • 将该基本类型数据抽象成类,在宿主类便可以灵活调用了。

臭名昭著的switch(Switch Statements)

  • 问题:switch最大的不好在于,他们将改变硬编码到代码中。
  • 解决方案:
    • 使用多态性替换switch。

平行继承体系(Parallel Inheritance Hierachies)

  • 问题:当你继承某个类的时候,发现同时需要在另一个继承体中加入对应的继承体系,才能完成特定功能。那么两个继承体系耦合程度太大。(sshotgun surgery霰弹式的变种)
  • 解决方案:
    • 可以使用Move Filed和委托将多余的继承体系消除

累赘类(Lazy Class)

  • 问题:某些类的作用微乎其微,或者毫无用处,又或者是你的子类根本就没有做什么。
  • 解决方案:
    • 你可以用Collapse Hierarchy或inline class等重构手法来去除系统中多余的类。就让它就义吧

夸夸其谈的未来性 (Speculative Generality)

  • 问题:想象在未来可能存在而加的没用到的代码,造成维护成本加高
  • 解决方案:
    • 使用Collapse Hierarchy,Inline Class等等方案

令人迷惑的临时值域(Temporary Filed)

  • 问题:在代码中,有可能发现一些临时变量或常量,但无法一眼就识别出他们的意图,此时这些变量就令你迷惑。
  • 解决方案:
    • 你可以使用Extract Class为这些无家可归的临时变量安个家,让他们更具有可读性。

过度耦合的消息链(Message Chains)

  • 问题: A对象调用B对象,B对象调用C对象,类似的情况,一旦出现修改消息链。你需要一一找到并修改。
  • 解决方案:
    • 过长的消息链的意图,直接封装成一个方法

过度的中间转手人(Middle Man)

  • 问题:打比方(A去买C的东西,需要经过B,B不存在一样可以A买C的东西)B对象的存在就是多余,或者更多B对象这样的人
  • 解决方案:
    • Remove Middle Man 删除这个中间人

不适当的亲密关系(Inappropriate Intimacy)

  • 问题: 如果A与B对象存在着一些相互关联的方法,相互调用,那么他们关系就有点乱了,需要整理了
  • 解决方案:
    • 可使用Move Field(使用字段),Move Method(移动方法)等将情投意合的代码段移到一起

异曲同工的类(Alternative Classes with Different Interface)

  • 问题:在不同的类,以不同的方法名称执行同样的方法。A文件的方法,在B文件也存在。重复
  • 解决方案:
    • 在同一个类中,使用 Rename Method和Move Method 将这些意图相同方法放到一个地方
    • 在不同类中,提取一个类来重构

不完美的类库(Incomplete Library Class)

  • 问题:使用前辈的类库,出现一些没有预测到的情况
  • 解决方案:
    • 添加扩展一些以满足需求的方法
    • 删除一些不需要的代码

存执的数据类(Data Class)

  • 问题:只有数据和一些数据访问方法的临时容器的类
  • 解决方案:
    • 运用Move Method等重构手法,为这些类添加能承担责任的方法
    • 如果上述方案不行,运用Move Field将他们移到自己应该去的地方

被拒绝的遗赠(Refused Bequest)

过多的注释(Comment)

  • 问题:注释过多造成代码可读性变差
  • 解决方案:
    • 用易读的方法名替代过多注释
    • 提取方法,减少提取方法的注释

相关参考

其他推荐阅读