24种代码坏味道以及其修改手法

1、神秘命名(Mysterious Name)

解决方法

①改变函数声明

②变量改名

③字段改名

 

2、重复代码(Duplicated Code)

 

①提炼函数:

同一个类的两个函数含有相同的表达式

 

②移动语句:

如果重复代码只是相似而不是完全相同,使用“移动语句”重组代码顺序,把相似的部分放在一起以便提炼

 

③函数上移:

重复代码位于同一超类的不同子类中,可以使用“函数上移”来避免在两个子类之间相互调用

 

3、过长函数(Long Function)

 

①提炼函数:

百分之九十九场景

 

②以查询取代临时变量

如果函数内有大量的参数和临时变量,会对我们函数的提炼造成阻碍,这时我们运用“提炼函数”,就会把过多的参数传递给被提炼的函数,可读性不见得就提升了。

因此,可以用“以查询取代临时变量”来消除这些临时元素。

 

③引入参数对象+保持对象完整

将过长的参数列表变的更简洁一些

 

④以命令取代函数

如果①-③执行之后,还是有过多的临时变量和参数,可以使用“以命令取代函数”

 

⑤分解条件表达式

条件表达式和循环常常也是提炼的信号,可以使用“分解条件表达式”来处理条件表达式

 

⑥多态取代条件表达式

对于过于庞大的switch语句,每个分支都应该用“提炼函数”变成独立的函数调用

如果有多个switch语句基于同一个条件进行分支选择,就应该使用“多态取代条件表达式”

 

4、过长的参数列表(Long Parameter List)

 

①以查询取代参数

 

②保持对象完整

 

③引入参数对象

 

④移除标记参数

 

⑤函数组合成类

 

5、全局数据(Global Data)

 

①封装变量

 

②最好将这个函数及其封装的数据搬移到一个类或模块中,只允许模块内的代码使用它,从而尽量控制其作用域

 

6、可变数据(Mutable Data)

 

①封装变量

确保所有数据更新操作都通过很少几个函数来进行,使其更容易监控和演进

 

②拆分变量

如果一个变量在不同时候被用于存储不同的东西,可以使用“拆分变量”将其拆分为各自不同用途的变量,从而避免危险的更新操作

 

③移动语句和提炼函数

尽量把逻辑从处理更新操作的代码中搬移出来,将没有副作用的代码与执行更新操作的代码分开

 

④查询函数和修改函数分离

设计API时,确保调用者不会调用到有副作用的代码,除非他们真的需要更新数据

 

⑤移除设值函数

缩小变量作用域

 

⑥以查询取代派生变量

如果可变数据的值能在其他地方计算出来,这就是一种特别刺鼻的坏味道

 

⑦函数组合成类/函数组合变换

限制需要对变量进行修改的代码量

 

⑧将引用对象改为值对象

如果一个变量在其内部结构中包含了数据,通常最好不要直接修改其中的数据,而是用“将引用对象改为值对象”令其直接替换整个数据结构

 

7、发散式变化(Divergent Change)

每当要对某个上下文做修改时,我们只需要理解这一个上下文,而不必操心另一个,即“每次只关心一个上下文”。

 

①拆分阶段

如果发生变化的两个方向自然地形成了先后次序(从库中取值,取值之后按照业务逻辑处理该值),可用“拆分阶段”将两者拆开,两者之间通过一个清晰的数据结构进行沟通

 

②搬移函数

如果①情况中,两个方向之间有更多来回的调用,就应该先创建适当的模块,然后用“搬移函数”把处理逻辑分开

 

③提炼函数

如果函数内部混合了两类处理逻辑,就应该先用“提炼函数”将其分开,再做②搬移函数

 

④提炼类

如果模块是以类的形式定义的,则可用“提炼类”来做拆分

 

8、霰弹式修改(Shotgun Surgery)

霰弹式修改类似于发散式变化,但又恰恰相反。如果每遇到某种变化,你都必须在许多不同的类内做出许多小修改,需要修改的代码散步四处,你不但很难找到它们,也很容易错过某个重要的修改,这就是霰弹式修改的体现。

 

①搬移函数+搬移字段

把所有需要修改的代码放进同一个模块里

 

②函数组合成类

如果有很多函数都在操作相似的数据,可以使用“函数组合成类”

 

③函数组合成变换

如果有些函数的功能是转化或者充实数据结构,可以使用“函数组合成变换”

 

④拆分阶段

如果一些函数的输出,可以组合后提供给,一段专门使用这些计算结果的逻辑,就可以使用“拆分阶段”

 

⑤使用内联相关的重构——内联函数/内联类

把本补钙分散的逻辑放到一起,完成内联后可能会有过长函数或者过大类的问题,再通过提炼相关的重构将其拆分成更合理的小块

 

9、依恋情结(Feature Envy)

一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于在自己所处模块内部的交流,即“依恋情结”的典型情况。

 

①搬移函数

某个函数为了计算某个值,从另一个对象那调用几乎半打的取值函数

 

②提炼函数

函数中只有一部分是①中的情况,把这一部分提炼到独立的函数中,再搬移过去

 

一些对抗发散式变化的模式破坏了这条规则,例如GoF的策略模式和访问者模式

 

10、数据泥团(Data Clumps)

①提炼类

总是绑在一起出现的数据应该有属于它们自己的对象

 

②引入参数对象/保持对象完整

缩短参数列表,简化函数调用

 

只要新对象可以取代两个或更多的字段,就值得这么做,不必在意数据泥团只用上新对象的一部分字段

 

11、基本类型偏执(Primitive Obsession)

 

①以对象取代基本类型

将原本单独存在的数据值替换为对象

 

②以子类取代类型码+以多态取代条件表达式

如果想要替换的数据值是控制条件行为的类型码

 

③提炼类+引入参数对象

有一组总是同时出现的基本类型数据--数据泥团

 

12、重复的switch(Repeated Switches)

 

①以多态取代条件表达式

任何switch语句都应该以多态取代条件表达式

 

13、循环语句(Loops)

 

①使用Java8中的流,取代循环

 

14、冗赘的元素(Lazy Element)

 

①内联函数/内联类

如果一个类在一开始设计时设计的很长远但最后一直只有一个函数在其内部;或者在重构的过程中,一个类只剩下了一个函数

 

②折叠继承体系

如果这个类处于一个继承体系中

 

15、夸夸其谈通用性(Speculative Generality)

 

①折叠继承体系

如果某个抽象类其实没有太大的作用

 

②内联函数/内联类

没有必要的委托

 

③改变函数声明

如果函数的某些参数未被用上;有并非真正需要,只是为不知元在何处的将来准备的入参去掉

 

④移除死代码

如果函数或类的唯一用户就是测试用例,请先删除测试用例,再将该函数或方法删除。

 

16、临时字段(Temporary Field)

①提炼类+搬移函数

 

②引入特例

在“变量不合法”的情况下创建一个替代对象,从而避免写出条件式代码

 

17、过长的消息链(Message Chains)

 

①隐藏委托关系

 

②提炼函数+搬移函数

先观察消息链最终得到的对象是用来干什么的,看看能否以“提炼函数”把使用该对象的代码提炼到一个独立的函数中,再运用“搬移函数”把这个函数推入消息链。

 

18、中间人(Middle Man)

 

①移除中间人

某个类的接口有一半的函数都委托给其他类,是一种过度运用,这时应该“移除中间人”,直接和真正负责的对象打交道

 

②内联函数

如果①中的情况,只有少数几个,可以运用“内联函数”把它放进调用端

 

③以委托取代超类/以委托取代子类

如果这些中间人还有其他行为,可以运用“以委托取代超类”/“以委托取代子类”把它变成真正的对象,这样既可以扩展原对象的行为,又不必负担那么多的委托动作。

 

19、内幕交易(Insider Trading)

①搬移函数+搬移字段

将两个频繁交换数据的模块“减少交流”

 

②新建模块

如果两个模块有共同的兴趣,可以再尝试新建一个模块,把这些公用的数据放在一个管理良好的地方

 

③隐藏委托关系,把另一个模块变成两者的中介

 

④以委托取代超类/以委托取代子类

继承常会造成密谋,子类对超类的了解都会超过超类的主观愿望,如果想要避免这样的情况,就需要运用“以委托取代超类”/“以委托取代子类”

 

20、过大的类(Large Class)

①提炼类

②提炼超类

③以子类取代类型码

识别出一个合适的功能子集

 

21、异曲同工的类(Alternative Classes with Different Interfaces)

只有两个类的接口一致时,才能将一个类替换。

 

①改变函数声明

将函数签名变的一致

 

②搬移函数

反复使用“搬移函数”将某些行为移入类中,直到两者的协议一致为止

 

③提炼超类

当②中搬移过程里造成了重复代码,或许可以用“提炼超类”提取一下

 

22、纯数据类(Data Class)

 

①封装记录

对于这样一个类,拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物,即纯数据类。这样的类是一种不会说话的数据容器,它们几乎一定被其他类过分细琐地操控着。这些类早期可能拥有public字段,若果真如此,立刻运用“封装记录”将它们封装起来

 

②移除设值函数

对于那些不应该被其他类修改的字段,请运用“移除设值函数”

 

③搬移函数

找出这些取值/设值函数被其他类调用的地点,尝试以“搬移函数”把那些调用行为搬移到纯数据类里来。

 

④提炼函数

如果③无法整个搬移,就运用“提炼函数”产生一个可被搬移的函数

 

23、被拒绝的馈赠(Refused Bequest)

子类应该继承超类的函数和数据。但如果子类不想或不需要继承,它们得到了所有的功能,却只能从中挑选几样来反复使用。按传统的说法,这意味着继承体系设计错误。

你需要

①为这个子类新增一个兄弟类

②函数下移+字段下移

把所有用不到的函数下推给那个兄弟

 

这样,超类就只持有所有子类共享的东西

 

③强烈一些的坏味道:以委托取代超类/以委托取代子类

如果子类复用了超类的行为,却又不愿意支持超类的接口,就请使用“以委托取代超类/以委托取代子类

”彻底划清界限。

 

24、注释(Comments)

当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余

 

①提炼函数

如果你需要注释来解释一块代码做了什么,试试“提炼函数”

 

②改变函数声明

如果函数已经提炼出来,但还是需要注释来解释其行为,试试“改变函数声明”为它改个好名字

 

③引入断言

如果你需要注释说明某些系统的需求规格, 试试“引入断言”

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值