第4章 代码坏味道
重构之前首先要知道什么样的代码需要改善,最常见的设计问题都出自这样的代码:重复、不清晰、复杂。
| 坏味道 | 重构 |
|---|---|
| 重复代码(Duplicated Code) | 形成 Templace Method 用 Factory Method 引入多态创建 链构造函数 用 Compsite 替换一/多之分 提取 Composite 通过 Adapter 统一接口 引入 Null Object |
| 过长函数(Long Method) | 组合方法 将聚集操作搬移到 Collecting Parameter 用 Command 替换条件调度程度 用 Strategy替换条件逻辑 |
| 条件逻辑太复杂(Conditional Complexity) | 用 Strategy替换条件逻辑 将装饰功能搬移到 Decorator 用 State 替换状态改变条件语句 引入 Null Object |
| 基本类型偏执(Primitive Obsession) | 用类替换基本类型 |
| 不恰当的暴露 | 用 Factory 封装类 |
| 解决方案蔓延 | 将创建知识搬移到 Factory |
| 异曲同工的类 | 通过 Adapter 统一接口 |
| 冗赘类 | 内联 Singleton |
| 过大的类 | 用 Command 替换条件调度程度 用 State 替换状态改变条件语句 用 Interpreter 替换隐式语言 |
| 分支语句 | 用 Command 替换条件调度程序 将聚集操作搬移到 Vistor |
| 组合爆炸 | 用 Interpreter 替换隐式语言 |
| 怪异解决方案 | 通过 Adapter 统一接口 |
重复代码(Duplicated Code)
类层次中不同子类里存在的明显/微妙的重复可以用 Template Method 来重构。
Factory Method 可以应对对象创建的不足。
链式构造函数可以去除类的构造法方法包含的重复代码。
如果有单独的代码处理一个对象或者一组对象,也许可以通过用 Composite 替换来去除重复。
如果对象处理方式的区别仅在于它们的接口不同,可以通过 Adapter 统一接口。
如果有条件逻辑用于处理空对象,而且相同的逻辑在整个系统中都是重复的,那么可以引入 Null Object 来消除重复。
过长函数(Long Method)
短函数胜于长函数的主要理由是逻辑的共享。小函数更方便逻辑的共享。
小的函数可以帮助理解代码。
可以有少数函数稍大一些,只要它们比较容易理解,而且不包含重复代码。
首先,优秀的设计人员都不会对代码进行不成熟的优化;其次,将许多小的函数调用串起来,所增加的性能开销一般微乎其微;最后,即使碰巧因此遇到性能问题,还可以通过重构来改进性能,而无需放弃消防法原则。
条件逻辑太复杂(Condition Complexity)
如果条件逻辑控制的是应该执行一种计算操作几个变形中的某一个,则可以考虑应用 Streategy 替换条件逻辑来重构。
如果条件逻辑控制的是应该执行类的核心行为之外某个特殊行为的若干段中的某一段,则可以使用装饰功能搬移到 Decorator 重构。
如果控制对象状态转换的条件表达式比较复杂,可以考虑通过应用 State 替换状态改变条件语句来重构。
处理空操作情形经常需要创建条件逻辑。如果在整个系统中有重复的相同的空调间逻辑,则可以使用引入 Null Object 来重构。
基本类型偏执(Primitive Obsession)
如果基本类型值控制着类中的逻辑,而基本类型并不是类型安全的,此时应该考虑用类替换类型代码。这将得到类型安全且能够扩展新行为的代码。
如果控制对象的状态转换的是使用基本类型值的复杂条件逻辑,那么可以使用用 State 替换状态改变条件语句。
如果控制算法运行的是非常复杂的条件逻辑,而且该逻辑还使用基本类型值,可以考虑应用用 Strategy 替换条件逻辑重构。
如果类有许多方法支持多个基本类型值的组合,那么就可能 存在隐式语言。这种情况下,可以考虑用解释器来替换隐式语言重构。比如 SQL 的多个细化查询函数重构为 SQL 解释器模式。
不恰当的暴露(Indecent Exposure)
在客户代码不应该看到的方法或者类,却对客户公开可见时,就会出现这种坏味。这种代码的暴露意味着客户了解在不太重要或者只有重要性的代码,这会增加代码的复杂程度。
用 Factory 封装类重构可以去除这种坏味,有些类应该只通过公共接口引用。
解决方案蔓延(Solution Sprawl)
如果许多类中都有用来完成某些职责的代码/数据,我们就说存在在解决方案蔓延。
将创建知识搬移到 Factory 重构可以解决对象创建职责蔓延的问题。
异曲同工的类(Alternative Classes with Different Interfaces)
如果两个类很相似,通常可以将它们重构为共享一个公共的接口。
但是,有时候不能直接修改类的接口,因为对代码没有控制权限。此时,可以应用通过 Adapter 统一接口重构为两个类生成一个公共接口。
冗赘类(Lazy Class)
类如果功能有限,缺乏存在价值,就应该删除。
经常能够遇到缺乏存在价值的 Singleton,此时可内联 Singleton 来进行重构。
过大的类(Large Class)
存在太多的实例变量,往往说明类的职责太多,一般使用提炼类和提炼子类来重构。
分支语句(Switch Statement)
当使用 switch 语句 使得使设计过度地复杂或者僵硬时,此时就需要基于对象或者多态的解决方案了。
组合爆炸(Combinatorial Expplosion)
当特定的条件和数据执行查询变得越来越多时,应该使用解释器模式来替换隐式语言,去除组合爆炸的坏味道。
怪异解决方案(Oddball Solution)
在系统中应该始终用一种方式解决一种问题,如果在同一系统中使用不同方式解决同一问题,就称之为怪异或者不一致的解决方案。
要去除这种重复,首先应该确定采用哪一种解决方案,通常可以应用替换算法来重构得到贯穿系统始终的一致方案。