软件配置管理(四)代码味道与重构

重构的概念及意义

重构是使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。提高其可理解性,降低修改成本。
重构可以改进软件设计、使软件更加容易理解、帮助找到软件缺陷、提高变成速度。

代码味道

指程序中存在的一些不良的编程或设计方案。可以作为重构的指示。

代码味道分类

1.类内味道

1.1 可度量的味道-Measured Smells

1.1.1 过长函数-Long Method

方法太长。
手段:

  • 把函数变小:提炼函数
  • 函数内有大量参数和临时变量:以查询代替临时变量
  • 参数太多:引入参数对象
  • 太多临时变量和注释:以函数对象取代函数
  • 条件表达式和循环:分解条件表达式

1.1.2 过大类-Large Class

一个类职责过多,拥有过多的实例变量。
手段:

  • 太多实例变量或太多代码:提炼类、提炼子类
  • 确定客户端如何使用:提炼接口
  • 把数据和行为移到一个独立的领域对象,但保留一些重复数据:复制“被监视的数据”。

1.1.3 过长参数列-Long Parameter List

一个方法需要传递太多的参数。
手段:

  • 向已有对象发出一条请求就可以取代一个参数:以函数取代参数
  • 参数缺乏合理的对象归属:引入参数对象
  • 将来自一个对象的参数收集起来:保持对象完整

1.1.4 过多的注释-Comments

在非必要时不要写注释,优先重构代码以使代码具有自解释性。
手段:

  • 需要注释来解释一块代码做了什么:提炼函数
  • 函数已经提炼出来,但仍需注释:函数改名
  • 需要注释来说明系统的需求规格:引入断言

1.2 不必要的复杂性-Unnecessary Complexity

1.2.1 夸夸其谈的未来性-Speculative Generality

当程序中过量的使用设计模式,导致在代码的阅读过程中很难找到主要的逻辑走向。放置过量的钩子或特殊情况来处理一些非必要的事情,可能在代码的编写调试过程中加深跟踪Bug的难度。
手段:

  • 某个抽象类没有太大作用:折叠继承体系
  • 不必要的委托:将类内联化
  • 函数的某些参数未被使用:移除参数
  • 函数名称带有多余的抽象含义:函数改名
  • 无用函数:内联函数、移除函数

1.3 重复-Duplication

1.3.1 重复代码-Duplicated Code

在多个地方发现相似代码结构,
手段:

  • 同一个类含有相同代码:提炼函数
  • 两个兄弟类有相同代码:提炼函数、函数上移、塑造模板函数
  • 两个不相干类有相同代码:提炼类

1.3.2 异曲同工的类-Alternative Classes with Different Interfaces

不同的类完成相同的任务,但有不同的签名(主要指方法签名)。
手段:

  • 两个函数做同一件事,但有不同签名:函数改名
  • 将某些行为移入类,直到两者协议相同:搬移函数
  • 必须重复而冗赘移入代码才能实现上述重构:提炼超类

1.4条件逻辑-Conditional Logic

1.4.1 Switch惊悚现身-Switch Statements

Switch语句(包括if-else)通常会导致代码重复。
手段:

  • 与类型码相关的函数或类:提炼函数、搬移函数、以多态取代条件表达式、以子类取代类型码、以状态模式或策略模式取代类型码。
  • 只在单一函数中有一些选择事件:以明确函数取代参数
  • 选择条件之一是null:引入null对象。

2.类间味道

2.1 数据-Data

2.1.1 基本类型偏执-Primitive Obsession

基本类型被过度使用。
手段:

  • 将原本单独存在的数据值替换成对象:以对象代替数据值
  • 如果想替换的是类型码,而类型码不影响行为:以类取代类型码
  • 如果有与类型码相关的条件表达式:以子类取代类型码、以状态模式、策略模式取代类型码
  • 如果有一组应该总是被放在一起的字段:提炼类
  • 如果在参数列中看到基本类型的数据:引入参数对象
  • 发现自己正从数组中挑选数据:以对象取代数组

2.1.2 纯稚的数据类-Data Class

类只拥有成员变量和对应的setter和getter方法。
手段:

  • 对于public字段:封装字段
  • 如果一个容器的字段没有得到恰当的封装:封装集合
  • 对于那些不该被其他类修改的字段:移除设值函数
  • 找出getter和setter被其他类运用的地点:搬移函数
  • 如果无法搬移整个函数:提炼函数
  • 将getter和setter隐藏起来:隐藏函数

2.1.3 数据泥团-Data Clumps

一些数据项同时出现在多个地方,如一对内的成员变量、多个方法签名的参数等。
手段:

  • 两个类有相同的字段或许多函数签名中的参数相同:提炼类
  • 缩短参数列表、简化函数调用:引入参数对象、保持对象完整

2.1.4 令人迷惑的暂时字段-Temporary Field

一个对象中某个变量仅为某些特定场合而设,导致代码难以理解。
手段:

  • 一个对象中某个实例对象仅为某种特定情况而定:提炼类
  • 在“变量不合法”的情况下创建一个null对象,从而避免写出条件式代码:引入null对象

2.2 继承-Inheritance

2.2.1 被拒绝的遗赠-Refused Bequest

子类继承父类的方法和数据,但仅使用其中一部分。
手段:

  • 子类不想或不需要继承超类(先为子类新建一个兄弟类):提炼子类、函数下移、字段下移
  • 子类复用了超类的行为,却又不愿意支持超类的接口,拒绝继承超类的实现:以委托代替继承

2.2.2 狎昵关系-Inappropriate Intimacy

类之间的关系变得过于紧密。
手段:

  • 类之间的关系过于紧密:搬移函数、搬移字段、将双向关联改为单向关联
  • 如果两个类存在共同点:提炼类、隐藏“委托关系”
  • 从继承体系中分离子类:以委托取代继承

2.2.3 冗赘类-Lazy Class

如果一个类不值其身价,它就应该消失。
手段:

  • 对于几乎没用的组件:将类内联化
  • 如果某些子类没有做足够的工作:折叠继承体系

2.3 职责-Responsibility

2.3.1 依恋情结-Feature Envy

一个方法对别的类的兴趣高于它本身所在的类。
手段:

  • 函数对某个类的兴趣高过对自身所处类的兴趣:搬移函数、搬移字段
  • 函数中只有一部分对其他类更感兴趣:提炼函数、搬移函数

2.3.2 过度耦合的消息链-Message Chains

一个对象请求另一个,后者在请求下一个对象,…这就是消息链【a.getB().getC().getD()】。采取这种方式,意味客户代码将与查找过程中的导航结构紧密耦合,一旦对象间的关系发生任何变化,客户端就不得不做出相应修改。
手段:

  • 隐藏“委托关系”

2.3.3 中间人-Middle Man

一个类的接口中,一半的方法都委托给了其他类。
手段:

  • 过度运用委托:移除中间人
  • 如果“不干实事”的函数只有几个:内联函数
  • 如果中间人还有其他行为,需要对原对象的行为进行扩展:以继承取代委托

2.4 协调变化-Accommodating Change

2.4.1 发散式变化-Divergent Change

一个类拥有多个引起它变化的原因。
手段:

  • 找出某特定原因而造成的所有变化:提炼类

2.4.2 霰弹式修改-Shotgun Surgery

进行某种修改时,必须对多个不同的类进行对应的小修改。
手段:

  • 把所有需要修改的代码放进同一个类中:搬移函数、搬移字段
  • 如果眼下没有合适的类可以安置这些代码,就创造一个
  • 把一系列相关行为放进同一个类中:将类内联化

2.4.3 平行继承体系-Parallel Inheritance Hierarchies

当为某个类增加子类时,不得不为另一个类也增加一个子类。
手段:

  • 让一个继承体系的实例引用另一个继承体系的实例:搬移函数、搬移字段

2.5 库类-Library Classes

2.5.1 不完善的程序库类-Incomplete Library Class

手段:

  • 如果只想修改库类的一两个函数:引入外加函数
  • 如果想添加大量额外行为:引入本地扩展

软件配置管理(四)代码味道与重构
https://buttering.github.io/EasyBlog/2021/06/14/软件配置管理(四)代码味道与重构/
作者
Buttering
发布于
2021年6月14日
许可协议