文章目录
参考《重构:改善既有代码的设计》
,书中所列的22种坏味道
在CR中基本都会遇到
-
Duplicated Code(重复的代码)
- 定义:在一个以上的地点看到相同或相似的代码结构
- 影响:想要修改其中一段代码逻辑需要修改多次,易遗漏,难维护
- 目标:消除重复,提升可维护性的目标
- 方法:提炼函数,移动语句,函数上移等重构方法。
-
Long Method(过长函数)
- 定义:一个函数包含了过多的逻辑功能或过分体现逻辑功能的实现细节,导致函数产生过长的代码块
- 影响:过长的函数往往意味着函数功能不单一;或过分呈现细节未进行抽象。造成可读性差和增加代码维护成本;过长函数还容易发生资源未释放等编码问题
- 改进目标:抽象待重构函数流程,分解出子函数,最大化精简出可读性强、功能单一的函数
- 方法:提炼函数:找到函数中适合集中在一起的部分,将他们提炼出来形成一个新函数,并用函数“做什么”来命名新的函数
-
Large Class(过大类)
- 定义:由于属性未分组和职责不单一而包含过多属性、方法和代码行的类
- 影响:随着属性、方法和代码行数的不断增加,重复代码接踵而至,最终走向混乱
- 改进目标:拆分过大的类,确保类职责单一
- 方法:
提取类
提取子类
提取接口/超类
-
Long Parameter List(过长参数列)
- 定义:方法的入参过多,或存在不必要的参数
- 影响:方法不易被理解、使用,方法签名容易不稳定,不易维护
- 改进目标:去除多余参数,合并部分参数,提升方法签名稳定性
- 方法:
- 以查询取代参数
- 保持对象完整
- 引入参数对象
- 函数组合成类
- 移除标记参数
- 注:有时候你明显不希望造成“被调用对象”与“较大对象”间的某种依赖关系。这时候将数据从对象中拆解出来单独作为参数,也很合情合理。但是请注意其所引发的代价。如果参数列太长或变化太频繁,你就需要重新考虑自己的依赖结构了。
-
Divergent Change(发散式变化)
- 定义: 某个模块经常因为不同的原因在不同的方向上发生变化
- 影响: 通常,发散式变化是由于多个变化方向之间有较多的来回调用或者函数内部混合了多类处理逻辑。当处于多个不同上下文的外部行为发生变化时候,都会引起对同一个类或模块的修改,影响了代码的可读和可维护性
- 改进目标: 提高代码组织结构、职责单一提升代码可读性、可维护性
- 方法: 拆分阶段、搬移函数、提炼函数、提炼类
-
Shotgun Surgery(霰弹式修改)
- 定义:霰弹式修改:指的是如果每遇到某种变化,你都必须在许多不同的类内做出许多小修改。此时,你所面临的坏味道就是霰弹式修改
- 影响:需要修改的代码散布四处,你不但很难找到它们,也很容易错过某个重要的修改。任何修改遗漏都会导致异常结果
- 改进目标:更好的代码组织;更少的代码重复;更易维护
- 方法:搬移函数、搬移字段、函数组合成类、函数组合成变换、拆分阶段、内联函数、内联类
-
Feature Envy(依恋情结)
- 定义:依恋情节/特性依恋: 一个函数跟另一个模块中的函数或数据交流格外频繁,远胜于在自己所处模块内部的交流
- 影响:可读性、可维护性低:调用另一模块功能时往往需要打一套组合拳才能完成,需要知道过多的细节;往往会伴随有“内幕交易、重复代码、霰弹式修改……”
- 改进目标:将函数搬移到对应的类,解除跨模块的过多交流
- 方法:提炼函数、搬移函数
- 注:策略模式、访问者模式往往会带来依恋情节,这不是说这两个模式不可取。我们需要理解:从根本上,我们消除“依恋情节”和应用这些设计模式都是为了把一起变化的东西放到一块儿。
-
Data Clumps(数据泥团)
- 定义:总是成块出现的相同数据项,包括多个类中相同的字段、多个方法签名中相同的参数等
- 影响:成块出现的重复参数过多,影响阅读和理解,难维护
- 改进目标:减少相同的字段及入参,缩短入参列,简化函数调用
- 方法:提炼类;引入参数对象;保持对象完整性
-
Primitive Obsession(基本型别偏执)
- 定义: 对于具有意义的业务概念如钱、坐标、范围等,不愿意进行建模,而是使用基本数据类型进行表示
- 影响: 暴露较多细节,代码内聚性差,可读性差
- 改进目标: 消除基本类型,提升代码可修改性、内聚性、可读性
- 方法:
- 1、对象取代基本类型
- 2、子类取代类型
- 3、多态取代条件表达式
- 4、提炼类
- 5、引入参数对象
-
Switch Statements(switch惊悚现身)
- 定义:在不同的地方反复使用同样的switch逻辑
- 影响:影响可维护性:每当需要增加一个选择分支时,必须找到所有switch,并逐一更新
- 改进目标:消除重复switch,提升代码可修改/可扩展能力
- 方法:多态取代条件表达式
-
Parallel Inheritance Hierarchies(平等继承体系)
- 定义:每当你为某个类添加一个子类,必须同时为另一个类相应添加一个子类。这种情况的典型特征是:某个继承体系的类名前缀或类名后缀完全相同。
- 影响:起初的继承体系很小,随着不断添加新类,继承体系越来越大,也越来越难修改。
- 改进目标:多继承
- 方法:让一个继承体系的实例引用另一个继承体系的实例。
-
Lazy Class(冗赘类)
- 定义:冗赘的元素主要包括由于过度设计或在代码演进过程中,产生的冗余、废弃[1]或不足以独立承担其职责的类、方法、变量等
- 影响:代码不简洁,存在多余的元素,造成在维护时无用修改,难以维护,影响代码的可读性。
- 改进目标:消除冗赘的程序元素,提高代码的可读性、可维护性。
- 方法
•内联函数或内联类
•如果这个类处于一个继承体系中,可以使用折叠继承体系
•安全删除冗余元素。 - 注[1]:《重构》一书中冗赘的元素定义中并未包含冗余、废弃的代码,但在公司代码中冗余、废弃等问题较为常见,故此处一并列出。
-
Speculative Generality(夸夸其谈未来性)
- 定义:过度的考虑程序的通用性
- 影响:过度的设计导致代码不易理解和维护
- 改进目标:删除过度设计的代码
- 方法
折叠继承体系
内联类
内联函数
改变函数声明
移除死代码
-
Temporary Field(令人迷惑的暂时值域)
- 定义:某个实例变量仅为代码中一小部分功能临时所用而创建
- 影响:通常一个对象会需要它的全部的变量。当一个变量看上去没什么用,却要试图了解他为什么在哪里时,会使类的作用变得更难理解,影响了代码的可读性和可维护性
- 改进目标:消除临时字段,提升代码可读性、可维护性
- 方法:提取类;引入特例
-
Message Chains(过度耦合的消息链)
- 定义:如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象……这就是消息链。在实际代码中你看到的可能是一长串取值函数或一长串临时变量。
- 影响:客户端代码将与查找过程中的调用结构紧密耦合。一旦对象间的关系发生任何变化,客户端就不得不做出相应修改。
- 改进目标:针对过长消息链,可以用这时候应该使用隐藏委托关系,把调用链这种耦合关系放在中间人中。
- 方法:
1.观察清楚消息链的调用业务逻辑。
2.通过提炼函数把所有相同调用放在一个函数中
3.搬移函数到对应的中间人类中(新增或者使用以前的类)
4.替换这个函数到之前调用链。隐藏委托关系 - 注:有些人把任何函数链都视为坏东西,我们不这样想。我们的冷静镇定是出了名的,起码在这件事上是这样的。
-
Middle Man(中间转手人)
- 定义:一种过度使用委托(某类中一半以上方法都委托给其他类)的代码[1]
- 影响:当需求发生某些变化的时候,作为中间人的代码总会被牵连一并修改,代码越发臃肿
- 改进目标:减少委托
- 方法:移除中间人、内联、以继承取代委托(旧版)、以委托取代超类/子类(新版)
- 注[1]:其中的“一半以上”并非一个绝对的衡量值,在日常开发中,我们可基于代码实际业务含义、以及自身对代码的容忍程度做判断其中的“一半以上”并非一个绝对的衡量值,在日常开发中,我们可基于代码实际业务含义、以及自身对代码的容忍程度做判断
- 注[2]:旧版以及新版重构一书中也提到了以继承取代委托,以委托取代超类或子类,实际工作中对中间人消除效果有限,此次课程暂不展开
-
Inappropriate Intimacy(狎昵关系)
- 定义:一个类大量使用另一个类的内部字段和方法。
- 影响:指的是两个类之间的依赖性过高,从而导致它们之间出现了双向的引用,甚至出现了相互调用,这会使代码难以修改、扩展和维护。
- 改进目标:通过将类之间的依赖性降低到最低限度来避免这种情况的发生
- 方法:
搬移函数(Move Method)
和搬移字段(Move Field)
来让类之间斩断羁绊。采用中介者模式、外观模式等设计模式
-
Alternative Classes with Different Interfaces(异曲同工的类)
- 定义: 异曲同工的类——两个类功能一致,却有不同的定义(方法/接口)
- 影响: 相关的业务逻辑可能会重复实现,或分布到不同类中,代码难维护
- 改进目标: 统一接口、相同的功能只实现一份
- 方法: 函数改名、搬移函数、添加参数、函数参数化、提炼超类、移除子类
注:
- 狭义的异曲同工:两个类功能完全相同;
- 广义的异曲同工:两个类有很多相同的功能,从抽象角度看它们也有高一致性,却没有共同的基类/接口。
-
Incomplete Library Class(不完美的程序库类)
- 定义: 程序库中的某个类功能不足,需要补充一些方法以满足客户端的需求,而客户端又无法修改这个类
- 影响: 这会导致客户端不得不在自己的代码中编写与这个类有关的逻辑,导致代码冗余和维护困难。
- 改进目标: 定义补丁
- 方法: 创建一个新类,继承这个程序库的类,并添加缺失的方法。这个新类就像是一个特制的补丁,补充了原有类的不足之处。这个新类可以封装原有的类,提供更完整的功能给客户端。
-
Data Class(幼稚的数据类)
- 定义: DataClass是指:它们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。这样的类只是一种不会说话的数据容器。
- 影响: DataClass将数据与行为间关系割裂,破坏了面向对象的精髓。特定数据的操作分散在代码各处,可能造成代码霰弹式修改
- 改进目标: 将特定数据的操作集中在一个地方,提高代码的可读性、可维护性、可扩展性等
- 方法: 封装变量、封装集合、搬移函数、抽取方法、移除设值函数
-
Refused Bequest(被拒绝的遗赠)
- 定义: 被拒绝的遗赠是指:对于某个子类,它只想继承基类的部分函数和数据,不需要基类提供的全部内容,这些不需要的内容就成为了子类的负担
- 影响: 这种坏味道通常影响并不大,但如果子类拒绝实现部分接口或者基类的方法只适用于某个子类特定的方法,就会对可维护、可扩展性等造成较大影响。
- 改进目标: 改进不合理的继承体系,使代码结构清晰、可控。
- 方法:
•函数/字段下移,让超类只持有子类共享的东西[1]
•以委托取代超类/子类 - 注[1]:并不建议对所有存在“被拒绝的遗赠”的代码都进行修改,我们经常使用继承复用一些行为,可以很好的应对日常工作,所以修改的成本和收益还是需要开发者自己权衡。但是当“被拒绝的遗赠”使开发人员困惑时,就建议及时处理掉。
-
Comments(过多的注释)
- 定义:
1.某段代码有大段的注释,这些注释之所以存在是因为代码很糟糕
2.代码注释错误,或者缺少必要的注释[1] - 影响: 以注释掩盖了代码坏味道,或者没有正确的注释,不便阅读。影响了代码的可读、可维护性
- 改进目标: 消除不必要的注释,补充必要的注释,提升代码可读可维护性
- 方法
1.提取变量(代码段注释)
2.提取方法(代码段注释)
3.方法/变量改名(方法/变量注释)
4.补充/修改必要的注释[2] - 注:[1] [2]《重构》一书中并无此条,但业务代码中确实存在缺少必要注释、注释错误等情况,比如Javadoc、算法注释等,因此专门列出
- 定义:
-
Insider Trading(内幕交易)
- 定义:模块之间互相引用,私下直接进行大量的数据访问和交换
- 影响:增大模块间的耦合,容易导致循环依赖,加快架构腐化,甚至会朝着大泥球式的架构发展,严重影响可维护性
- 改进目标:消除模块间不合理的依赖关系(特别是循环依赖),将私下的数据访问和交换放到明面上,使模块间解耦,提高可维护性
- 方法:搬移函数、搬移字段、隐藏委托关系、以委托取代子类/超类