美文网首页
重构--改善既有代码的设计

重构--改善既有代码的设计

作者: 逍遥游lx | 来源:发表于2019-04-16 15:28 被阅读0次

    继承取代委托

    即上一条的反例

    作者:RobinYeung
    链接:https://www.jianshu.com/p/7ba839abff3b
    来源:简书

    前言

    任何一个傻瓜都可以写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员

    重构的意图

    重构不产生新的功能,狭义范围来说也不修复原有的bug

    重构:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本

    改进软件设计
    使软件更容易理解
    帮助找到bug
    提高编程速度

    重构的时机

    过早的断言可扩展性容易造成过度设计
    在前期设计合理的前提下,增加新特性或修复bug时才是最合适重构的契机
    此时你能更好的理解什么地方是变化的,什么地方是设计不合理的

    第一步:重构的前提

    人非圣贤,孰能无过。我们无法保证重构不会引入bug,所以只能保证引入可靠的测试。

    第一步:保证对即将修改的代码提供一个可靠的测试环境

    越是庞大的软件,花时间去编写单元测试,在重构是会带来约巨大的便利
    确保所有测试是完全自动化,让他们检查自己的测试结果
    一套好的测试能大大缩减查找bug所需要的时间

    第二步:找出代码的怀味道

    如果尿布臭了,就换掉它

    基本问题:重复、冗余

    重复代码

    范例:代码重复1次令人难受,重复2次就该考虑重构。
    问题:重复,复用差
    解决:提出方法/类:增强复用; 模板模式:增强复用

    过长函数

    范例:略
    问题:可读性差,逻辑混乱,可维护性差,可扩展性差
    解决:

    每当需要注释说明代码块功能时,就该提出方法了
    消除临时变量
    引入参数对象
    引入方法代理
    复杂的if-else Switch 提出处理方法

    过大的类

    范例:略
    问题:可读性差,逻辑混乱,可维护性差,可扩展性差
    解决:提出类/子类/代理类

    switch、if-else 泛滥

    范例:散落各处的switch,到处都是switch
    问题:重复、可维护性差
    问题:最大隐患是case被多个switch使用,如此一来修改一个case需要寻找所有相关的switch。
    解决:尽量用多态代替多个Switch等判断,而且switch尽量提出到工厂方法这样的最前的判断中。状态模式、策略模式、工厂模式。

    临时字段

    范例:只被用一次,又不需要它代替注释的临时字段、临时变量
    问题:冗余
    解决:inline

    过度耦合的消息链

    范例:过长的引用链或调用链。有中间商(Adapter)赚差价不可怕,可怕的是你穿衣服要别人帮忙
    问题:耦合度过高
    解决:Move method,重新设计分层;EventBus(不得已之选)

    异曲同工的类

    范例:两个类实际在做类似的事情
    问题:冗余
    解决:合并

    过多的注释

    范例:略
    问题:注释只在迫不得已的时候用
    解决:方法名、变量名、常量名 代替注释

    冗余类

    范例:直接可以inline的多余类
    问题:冗余。其实和基本类型偏执是矛盾的,但凡事过犹未及
    解决:删除

    领域、边界

    过长参数列

    范例:略
    问题:可读性差,参数关联性不被重视
    引入参数对象

    数据泥团

    范例:类似过长参数列,主要是有相关性的数据不被归类整理,甚至存在多个副本,被散落到多个类去处理
    问题:可读性差,参数关联性不被重视
    解决:引入参数对象。最简单的例子 Size。

    发散式变化

    范例:一个变化导致了多个方法甚至多个类都需要修改
    问题:可维护性差、领域边界问题
    解决:软件一但需要修改,我们希望只在一点进行修改。如果做不到这点,这代码的味道就相当刺鼻。最简单的例子,提出常量

    散弹式修改。

    范例:这是上一点的升级版,如果需要修改的代码散布四处,你不但很难找到她们,也很容易忘记某个重要的修改。(对于我们的代码来说,甚至散落到不同应用中)
    问题:可维护性差、领域边界问题
    解决:类似上一点

    依恋情节

    范例:一个类的方法对另一个类的属性的依赖高过自己本身,如为了计算某值经常从另一个对象那调用了n次
    问题:设计问题
    解决:Move method

    基本类型偏执

    范例:滥用基本数据类型,极少提出类;魔数到处是,常量到处放
    问题:数据关联性不被重视,魔数泛滥可能造成重复和发散式变化
    解决:相互关联的数据,有必要抽到数据对象中而不是都使用基本数据类型。常量也应该分门别类。

    平行继承

    范例:当为一个类增加一个子类,必须为另一个类增加相应子类
    问题:发散式变化
    解决:引用代替继承

    狎昵关糸

    范例:领域不清,造成太多变量、方法无法private
    问题:违反迪米特法则(对别的对象保持最少的了解)领域边界问题
    解决:划清界限,把关注点提出到新类

    被拒绝的馈赠

    范例:给子类暴露了一堆用不上的属性、方法
    问题:领域边界问题
    解决:不该暴露给子类的要坚决隔离

    纯数据类

    范例:没有方法的JavaBean
    问题:过度设计
    解决:担负更多工作,能对数据进行自管理

    过度设计

    夸夸其谈的未来性

    范例:多余的抽象
    问题:冗余
    解决:用不到就别挡路

    中间人 委托的过度使用

    范例:一群中间商赚差价(Adapters)
    问题:过度设计
    解决:精简架构。

    不完美的库类

    范例:抽出来做库却没人去复用
    问题:过度设计。
    解决:评估复用性

    吐血新的

    一个类里面不要做那么多事
    不要用魔数
    不要用标志位来区分行为
    OSD PowerProcess VideoRecordeService
    不要滥用静态方法
    switch的位置 越前面越好

    第三步 实践重构方案
    重新组织方法

    提出方法 Extra method

    动机:主要想要注释的代码段 都应提出;保证方法的职责单一

    内联方法 Inline method

    动机:与Extra method恰恰相反。当一个方法就只有一行代码,同时又未被复用,同时即便没有名字功能也足够清晰时没必要单独存在

    内联临时变量 Inline temp

    参考Inline method
    如果需要让人知道其中作用可以提出到方法(?

    Query方法取代临时变量

    与 Inline method,Inline temp恰恰相反。有一种情况是你需要一个方法名来作为注释
    为什么不用变量?因为临时变量无法被复用

    引入解释性变量

    好吧,又跟上一点矛盾了。因为我根本不可能去复用它

    分解临时变量

    除了记录循环(fori),收集结果两种情况的临时变量,都不应被二次赋值,否则往往意味着承担了一项以上职责

    移除对参数的赋值

    类似上一条原则

    方法对象取代方法

    原则 只要将相对独立的代码从大型函数中提取出来,就可以大大提高代码的可读性
    但局部变量的存在会影响我们提出方法的可行性。那么此时你就需要把方法和局部变量一起提出到一个新的对象里。

    替换算法

    即优化简化算法,没什么好讲的

    在对象之间搬运特性

    搬运方法

    将旧方法变成一个单纯的委托方法或搬运旧方法到更需要它的类

    搬运字段

    正常来说字段不应该为public 如果必须是public 则应搬运到字段类

    提出类 Extra class

    让类的职责更单一;让类得到更好的抽象和复用

    内联类

    与上一点相反。如果提出并非必要(本身功能不够独立)

    隐藏委托关系

    委托关系只应该在委托,被委托双方之间发生耦合,不应该被场景类耦合(三端耦合)
    MVC就是典型的三端耦合

    移除中间人

    与上一点相反,如果被委托方变成单纯的中间人,则可以考虑移除
    打个比方,如果View和Model简单到可以直接相互调用,根本不需要再经过一个Presenter

    引入外部方法

    当一个类没必要对某项业务亲自去实现时
    打个比方,吃蛋糕的人没必要去知道蛋糕怎么做

    引入本地扩展

    很遗憾,除了小明真的没人对蛋糕感兴趣,那还是他自产自销吧

    重新面对数据

    封装字段 / 自封装字段

    即setter / getter
    相比直接暴露字段,可以做更多前置操作,如懒加载、前置处理(过滤等)

    对象取代数据

    一开始你以为你只需要一项数据,到后来你发现它跟多项数据关联(电话、区号、归属地、姓名等等)
    当关联数据逐渐庞大,对象更易管控,程序员的思维总是树形的

    对象取代数组

    有时数组的index意味着协议(事先约定),我们很难去记住属于第一位是人名这样的约定(举例McuCommand)

    复制被监视数据

    用观察者模式同步可能被多处引用的数据(比如更新ui)

    单向关联改为双向关联

    添加反向引用,可以同时更新双方状态(经典双向关联 V - P)

    双向关联改成单向

    与上一点相反,问题在于容易发生泄露

    常量取代魔数

    基本的。让人能读懂那个该死的数字是啥。同时避免发散式变化

    封装集合

    增加可读性
    通过Type去避免混用

    数据类取代记录

    把相关的记录整理到一个数据类中,便于后续扩展出处理这些数据的方法

    类型代替类型码

    缺乏关联性,甚至如果重了还会导致业务漏洞

    子类、策略对象、状态对象取代类型码

    如果类型码决定了业务行为,那可以直接通过子类取代类型码来避免过多的switch和代码杂糅
    状态模式、策略模式

    简化条件表达式(条件表达式本来就是复杂而容易出错的地方)

    分解条件表达式

    条件逻辑已经很复杂了,不要在条件分支下写一堆代码。而且分支本身就表明了一定含义
    每个分支的执行都提出单独方法,给与名字

    合并条件表达式

    执行相同逻辑的分支,把条件合并到一个分支,并把判断条件提炼成一个方法

    合并重复条件

    IDE都会提醒

    移除控制循环标记

    用break和return去控制循环退出,提高可读性

    return取代条件表达式嵌套

    如题

    多态取代条件表达式

    与上一章 子类、策略对象、状态对象取代类型码 类似

    空对象取代Null

    Null过于暴力,导致空指针异常,并且缺乏可控性。

    引入断言

    避免参数异常导致问题

    简化方法调用

    方法改名

    给你的方法取个好名字,让人知道它具体是做什么的,不要怕方法名过长,反正会混淆的

    添加参数

    传入执行方法需要的更多信息
    避免相关函数做太多重复的事
    但警惕引入过长,过于难懂的参数列

    移除参数

    可能根本用不上这个参数,直接移除
    参数难以理解,移除

    查询方法(getter)和修改方法(setter)分离

    修改方法是产生副作用的,查询方法不应该产生副作用。
    明确职责,避免遇到线程和并发带来的问题

    参数对象 / 方法 取代参数

    保持对象完整,使参数更稳固,可读性更好
    用方法取代参数就更提高了可读性和可扩展性
    避免数据泥团

    移除不必要的setter

    如果不希望字段改变,不要个setter

    隐藏方法

    没必要暴露的方法不要暴露

    工厂方法取代构造方法

    方便扩展构建行为(单例、对象池等)

    封装向下转型(downcast)

    就是尽早的让对象有明确的类型,单一职责

    异常代替错误码

    能清晰的让你看到那些地方是异常处理

    测试取代异常

    与上一条有些许相悖
    主要避免异常被滥用

    处理抽象关系

    提炼接口

    依赖抽象=依赖稳定

    字段上移

    子类之间是平级关系,不应该互相依赖彼此的字段

    方法上移、提炼超类

    如果是通用的方法,提到超类中实现,避免重复代码

    构造方法本体上移

    与上一条类似,避免重复代码

    字段、方法下移、提炼子类

    与以上相反
    如果超类的方法,根本不在部分子类关心的被调用的范畴,应该提到一个中间超类或下移到关心它的子类

    折叠继承

    折叠过度设计的继承

    塑造模板方法

    模板模式

    委托取代继承

    子类根本不完全使用超类的所有特性,把超类换成委托对象来完成业务即可,避免过度设计

    相关文章

      网友评论

          本文标题:重构--改善既有代码的设计

          本文链接:https://www.haomeiwen.com/subject/gpmbwqtx.html