Bad Smell1
Duplicated Code
Case1: in different methods of same class
Extract Method
Case2: in brother class
Extract Method, Pull Up Method
Case3: in brother classes, same in algorithm structure
Extract Method, Form Template Method
Case 4: in different classes
Extract Class, but consider it serious, since it will bring coupling
Long Method
Smell
关键不在于函数多长,而在于函数“做什么”和“如何做”之间的语义距离
每当感觉需要点注释的时候,就是提取方法的时候
Case1: 99% cases
Extract Method
Case2: too many temps and parameters after extract methods
Replace Temp with Query, Introduce Parameter Object and Preserve Whole Object
Case3: still too many temps and parameters after done above
Extract Method Object
Case4: conditions
Decompose Conditional
Case5: loops
Extract Loops to Method
Large Class
Smell
类并非在所有时刻都使用所有实例变量
Case1: 提炼新的组件
Extract Class
Case2: 新的组件适合做子类
Extract Subclass
Case3: 运用Extract Interface为每一种客户端的使用方式抽取接口,可以帮助看清楚如何分解这个类
Long Parameter List
Case1: 如果向一个已有的对象发一个请求就可以取代一个参数
Replace Parameter with Method
Case2: 来自同一个对象的一大堆数据
Preserve Whole Object
Case3: 某些数据缺乏合理的对象归属
Introduce Parameter Object
例外: 引入对象其实会造成依赖关系,这时候可以将数据从对象拆解出来单独作为参数。两种方式权衡利弊。
Divergent Change
Smell
一个类受多种变化影响
一个类只应为一种外界原因而变化
找出某特定原因造成的所有变化
Extract Class
Shotgun Surgery
Smell
一个变化影响多个类
跟Divergent Change刚好相反。他们的最终的目标都是让一个变化对应一个类。
把所有需要修改的代码放进同一个类中
Move Method, Move Field
Inline Class (Extract Class相反)
Feature Envy
Smell
函数或函数的一部分对某个类的兴趣,高过对自己类的兴趣
数据和引用这些数据的行为应该一起变化
Case1: 函数
Move Method
Case2: 函数的一部分
Extract Method, Move Method
例外:Strategy和Visitor模式将少量需要被覆写的行为隔离开来,使得你可以轻松修改函数行为。当然也付出了“多一层间接性”的代价
Bad Smell2
Data Clumps
Smell
总是绑在一起出现的数据
删掉众多数据中的一项,如果其他数据因而失去意义,就是个明确信号:他们应该产生一个新对象。
Case1:以字段形式出现
Extract Class
Case2:以参数形式出现
Introduce Parameter Object或者Preserve Whole Object
一旦拥有新对象,就可以寻找Feature Envy
Primitive Obsession
Smell
大量使用基本类型
走出传统的洞窟,进入对象世界
Case1:将原本单独存在的数据替换为对象
Replace Data Value with Object
Case2;想要替换的数据是类型码,而他并不影响行为
Replace Type Code with Class
Case3:有与类型码相关的条件表达式 (类型码决定不同行为),宿主类可以被继承
Replace Type Code with Subclass
Case4:类型码的值在对象生命周期中变化 或 其他原因使得宿主类不能被继承
Replace Type Code with State/Strategy(创造另一个继承体系)
Case5:在数组中挑选数据
Replace Array with Object
Switch Statements
Smell
同样的switch语句散布在不同地点。一旦修改,就需要修改所有地方
一看到switch,就考虑是否可以以多态替代它
Case1:switch根据类型码进行选择
Replace Type Code with Subclasses 或 Replace Type Code with State/Strategy
然后Replace Conditional with Polymorphism
Case2:只在单一函数中有些选择事例,那么多态有点杀鸡用牛刀了。
Replace Parameter with Explicit Methods
Case3:如果选择条件之一是null
Introduce Null Object
Parallel Inheritance Hierarchies
Smell
Shotgun Surgery的特殊情况
每当为某个类增加一个子类,必须也为另一个类相应增加一个子类
一般策略是:让一个继承体系的实例引用另一个继承体系的实例
再运用Move Method和Move Field,将引用端的继承体系消弭于无形
Lazy Class
Smell
重构使得某个类身形缩水
事前添加了一个类来应付某些变化,但实际上并没有发生
Case1:如果某些子类没有做足够的工作
Collapse Hierarchy
Case2:几乎没用的组件
Inline Class
Speculative Generality
Smell
"我想我们总有一天需要做这事"
以各种各样的钩子和特殊情况来处理一些非必要的事情,造成系统更难理解和维护
Case1:某个抽象类其实没有太大作用
Collapse Hierarchy
Case2:不必要的委托
Inline Class
Case3:函数的某些参数未被用上
Remove Parameter
Case4:函数名称带有多余的抽象意味
Rename Method
Case5:函数或类的唯一用户是测试用例
删除代码和测试代码
Temporary Field
Smell
对象某些实例变量仅为某种特定情况而设,大多数情况下不需要用到
Case1:类中有个复杂算法,需要好几个变量。由于实现者不希望传递一长串参数,所以把这些参数都放进字段中
Extract Class提炼成函数对象
Bad Smell3
Message Chain
Smell
用户向一个对象请求另一个对象,然后再向后者请求另一个对象。。。
实际代码中你看到的可能是一长串get方法或临时变量
客户代码将与查找过程中导航结构紧密耦合,一旦对象间的关系发生任何变化,客户端就不得不作出相应修改
Case1:往往会
在消息链的不同位置使用Hide Delegate,但往往会把一系列对象变成Middle Man
Case2:更好的选择
观察消息链最终得到的对象是用来干什么的,以Extract Method把这段代码提炼到一个独立函数,再使用Move Method把这个函数推入消息链
并不是任何函数链都是坏东西
Middle Man
Smell
过度使用委托
Case1:过度运用委托
Remove Middle Man (与Hide Delegate相反),直接和真正负责的对象打交道
Case2:只有少数几个函数“不干实事”
Inline Method
Case3:为受托类整个接口编写简单的委托函数,而自己又有一些额外的方法
Replace Delegation with Inheritance
Inappropriate Intimacy
Smell
两个类过分亲密,花费太多时间探究彼此的private元素
Case1:两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性
Change Bidirectional Association to Unidirectional
Case2:如果两个类确实彼此需要
Extract Class把两者共同点提炼出来,让他们使用这个新类
Case3:继承往往造成过度亲密,子类对超类的了解总是超过后者的主观意愿。如果你觉得该让这个孩子独立省后。
Replace Inheritance with Delegation
Alternatives Classes with Different Interfaces
Smell
异曲同工的类
Case1:两个函数做同一件事情,却有着不同的签名
Rename Method
Case2
Extract Superclass
Incomplete Library Class
Smell
库往往构造的不够好,而且往往不可能让我们修改其中的类
Case1:需要为提供服务的类增加一个函数
Introduce Foreign Method
Case2:想要添加一大堆额外行为,或者有许多类需要同样的外加函数
Introduce Local Extension
Data Class
Smell
Data Class:拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物
Data Class可以作为一个起点,但它必须承担一定责任
Case1:public field
Encapsulate Field
Case2:包含容器类字段,避免它在宿主对象完全不知情的情况下被人修改
Encapsulate Collection
Case3:不该被其他类修改的字段
Remove Setting Method
然后找出取、设值函数使用的地点,使用extract method和move method把相关行为移到Data Class。之后可以运用hide method隐藏取值设值函数
Refused Bequest
Smell
子类不想或不需要继承超类的函数和数据
如果Refused Bequest引起困惑和问题,请遵循传统忠告:所有超类都应该是抽象的。但不必每次都这么做,因为它的坏味道很淡。
Case1:子类复用了超类的行为,却又不愿意支持超类的接口。Refused Bequest坏味道就变得很浓烈。
Replace Inheritance with Delegation
Comments
Smell
一段代码有长长的注释,这些注释之所以存在,是因为代码很糟糕
Case1:需要注释来解释一段代码做了什么
Extract Method
Case2:函数已经提炼,但还是需要注释解释其行为
Rename Method
Case3:如果需要注释说明某些系统的需求规格
Introduce Assertion
用各种方法重构糟糕的代码,最后会发现注释已经变得多余。当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余。
重构原则
重构定义
对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本
为何重构
改进软件设计
更容易理解
帮助找到bug
提高编程速度
重构和设计
重构带来更简单的设计,同时又不损失灵活性
降低了设计过程的难度,减轻了设计压力。不必再预先思考过多的灵活方案
重构和性能
构造良好的代码可以让你更加快速添加功能,也就有了更多时间优化性能
面对构造良好的程序,性能分析便有了较细的粒度,度量工具可以把你带入范围较小的程序段落中,性能调整也变得容易。
构筑测试体系
测试是重构的防护墙