0%

学习重构:代码的坏味道

读完了重构,感觉受益匪浅,准备写一个系列的文章。

这篇讲代码的坏味道,学习它是为了提升我们的眼力,为找到对应的重构方法做准备

笔记

缺少封装

全局数据(Global Data)

症状: 全局作用域的变量, 类变量, 单例

分析: 从代码库的任何一个角落都可以修改它,而且没有任何机制可以探测出到底哪段代码做出了修改。最刺鼻的坏味道之一。

可变数据(Mutable Data)

症状: 将可变对象作为函数参数

分析: 难以追踪数据变更, 定位故障

过长参数列表(Long Parameter List)

症状: 函数需要的参数很多, 如4个以上的必填参数

分析: 额外损耗调用者的心智, 也是函数违背了单一职责的征兆.

基本类型偏执(Primitive Obsession)

症状: 用基本类型来代表业务对象, 如钱、坐标、范围、人名等;

分析: 缺少必要的抽象, 容易诱发数据泥团, 也容易产生重复代码.

过长的消息链(Message Chains)

症状: 超长的链式调用, 或者一长串取值函数, 或一长串临时变量.

分析: 系统难理解和维护, 客户端代码将与查找过程中的导航结构紧密耦合。一旦对象间的关系发生任何变化,客户端就不得不做出相应修改。

不当封装

发散式变化(Divergent Change)

症状: 一处代码包含了多个上下文, 也就是承担了多个职责.

分析: 难以测试; 增大修改的难度, 因为要关注多个上下文.

霰弹式修改(Shotgun Surgery)

症状: 一个上下文, 被分散在多处, 每遇到某种变化,你都必须在许多不同的类\函数\模块内做出许多小修改.

分析: 与发散式变化类似, 难以测试, 难以修改.

依恋情结(Feature Envy)

症状: 一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于在自己所处模块内部的交流.

分析: 错误的抽象, 导致不同上下文的交汇, 难以测试.

内幕交易(Insider Trading)

症状: 不同模块之间互相调用过多

分析: 说明这两个模块的封装不恰当, 模块的函数或者数据放错地方了.

缺少抽象

重复代码(Duplicated Code)

症状: 在一个以上的地点看到相同的代码结构

分析: 影响可读性, 阅读时需要花额外的精力,比较多个地方是否存在差异。

过长函数(Long Function)

症状: 函数很长(超过100行), 大量的参数和临时变量, 复杂的循环和分支

分析: 影响可读性, 逻辑复杂难以测试

数据泥团(Data Clumps)

症状: 扎堆出现的多个数据项, 删掉众多数据中的一项, 其他数据就失去了意义; 例如: 多个类的相似属性, 多个函数的多个相同参数.

分析: 缺少必要的抽象, 开发者关注了过多细节; 难以修改

重复的switch (Repeated Switches)

症状: 出现在多个地方的相同switch语句, 或者连续的if\else.

分析: 是重复代码的一种, 也会引发霰弹式修改; 容易引发bug

循环语句(Loops)

症状: 过多的循环, 嵌套的循环

分析: 难以一眼看出循环代码的意图, 常演变成在一个循环里做多件事情.

过大的类(Large Class

症状: 单个类做太多事情, 有些字段或者函数关联性不高

分析: 与过长函数类似, 违背了单一职责原则.

过度抽象

冗赘的元素(Lazy Element)

症状: 可有可无的代码元素; 如: 只有一个方法的类, 只有一个子类的父类.

分析: 过度设计的典型, 难以应对变化. 如无必要, 勿增实体.

夸夸其谈通用性(Speculative Generality)

症状: 为了通用性而预先设计的代码结构, 实际上去很少用到; 如: 预留许多钩子处理各种非必要的事情, 无实际意义的多层继承体系.

分析: 过度设计, 系统更难理解和维护。

临时字段(Temporary Field)

症状: 预留的字段或者变量, 仅为某种特定情况而设.

分析: 过度设计, 代码让人不易理解,因为你通常认为对象在所有时候都需要它的所有字段。

中间人(Middle Man)

症状: 没有必要的中间人对象, 过度使用委托. 如: 某个类的接口有一半的函数都委托给其他类.

分析: 过度设计

不当抽象

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

症状: 在不同继承体系的类, 做的事情却大同小异

分析: 重复代码的一种类型

纯数据类(Data Class)

症状: 只有数据字段和读写函数的类

分析: 纯数据类常常意味着行为被放在了错误的地方, 使得难以追踪数据的修改.

被拒绝的遗赠(Refused Bequest)

症状: 子类和父类的相似度很低; 如: 子类只需要父类的一个方法, 却继承了整个类.

分析: 意味着继承体系设计错误

其他

神秘命名(Mysterious Name)

症状: 不能清晰地表明自己的功能和用法, 或者随意地取无意义的名称.

分析: 对可读性有很大影响, 如果你想不出一个好名字,说明背后很可能潜藏着更深的设计问题。

注释(Comments)

症状: 超长的的注释

分析: 意味着相关代码实现难以理解

思维导图

代码的坏味道

坏味道与重构手法速查表

坏味道(英文) 坏味道(中文) 原书页码 常用重构
Alternative Classes with Different Interfaces 异曲同工的类 83 改变函数声明(124),搬移函数(198),提炼超类(375)
Comments 注释 84 提炼函数(106),改变函数声明(124),引入断言(302)
Data Class 纯数据类 83 封装记录(162),移除设值函数(331),搬移函数(198),提炼函数(106),拆分阶段(154)
Data Clumps 数据泥团 78 提炼类(182),引入参数对象(140),保持对象完整(319)
Divergent Change 发散式变化 76 拆分阶段(154),搬移函数(198),提炼函数(106),提炼类(182)
Duplicated Code 重复代码 72 提炼函数(106),移动语句(223),函数上移(350)
Feature Envy 依恋情结 77 搬移函数(198),提炼函数(106)
Global Data 全局数据 74 封装变量(132)
Insider Trading 内幕交易 82 搬移函数(198),搬移字段(207),隐藏委托关系(189),以委托取代子类(381),以委托取代超类(399)
Large Class 过大的类 82 提炼类(182),提炼超类(375),以子类取代类型码(362)
Lazy Element 冗赘的元素 80 内联函数(115),内联类(186),折叠继承体系(380)
Long Function 过长函数 73 提炼函数(106),以查询取代临时变量(178),引入参数对象(140),保持对象完整(319),以命令取代函数(337),分解条件表达式(260),以多态取代条件表达式(272),拆分循环(227)
Long Parameter List 过长参数列 74 以查询取代参数(324),保持对象完整(319),引入参数对象(140),移除标记参数(314),函数组合成类(144)
Loops 循环语句 79 以管道取代循环(231)
Message Chains 过长的消息链 81 隐藏委托关系(189),提炼函数(106),搬移函数(198)
Middle Man 中间人 81 移除中间人(192),内联函数(115),以委托取代超类(399),以委托取代子类(381)
Mutable Data 可变数据 75 封装变量(132),拆分变量(240),移动语句(223),提炼函数(106),将查询函数和修改函数分离(306),移除设值函数(331),以查询取代派生变量(248),函数组合成类(144),函数组合成变换(149),将引用对象改为值对象(252)
Mysterious Name 神秘命名 72 改变函数声明(124),变量改名(137),字段改名(244)
Primitive Obsession 基本类型偏执 78 以对象取代基本类型(174),以子类取代类型码(362),以多态取代条件表达式(272),提炼类(182),引入参数对象(140)
Refused Bequest 被拒绝的遗赠 83 函数下移(359),字段下移(361),以委托取代子类(381),以委托取代超类(399)
Repeated Switches 重复的switch 79 以多态取代条件表达式(272)
Shotgun Surgery 霰弹式修改 76 搬移函数(198),搬移字段(207),函数组合成类(144),函数组合成变换(149),拆分阶段(154),内联函数(115),内联类(186)
Speculative Generality 夸夸其谈通用性 80 折叠继承体系(380),内联函数(115),内联类(186),改变函数声明(124),移除死代码(237)
Temporary Field 临时字段 80 提炼类(182),搬移函数(198),引入特例(289)