决定何时重构、何时停止和知道如何重构一样重要!
开发者必须通过实践培养自己的经验和直觉,培养出自己的判断力:学会判断一个类内有多少个实例变量算是太大、学会判断一个函数内有多少行代码才算太长。
-
重复代码(Duplicated Code)
如果你在一个以上的地方看到相同的程序结构,那么可以肯定:设法将它们合而为一,程序会变得更美好!你需要决定这个重复的代码放在哪里比较合适,并确保它被安置之后就不会在别的地方再次出现。 -
过长函数(Long Method)
程序越长越难以理解。现代OO语言几乎完全免去了进程内的函数调用开销,因此,你应该积极地分解函数。我们应该遵循原则:每当需要以注释来说明点什么的时候,我们就需要把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。如何确定需要提炼哪一段代码:寻找注释、条件表达式、循环。 -
过大的类(Large Class)
如果你发现一个类试图做太多事情,其内部就会出现很多不相关的实例变量,此时这个类的职责就不明确了。 -
过长参数列(Long Parameter List)
太长的参数队列难以理解,太多参数的接口对于使用者来说十分不友好,而且容易出错。如果可以使用一个对象代替参数列表,那么就应该这么做。 -
发散式变化(Divergent Change) VS 霰弹式修改(Shotgun Surgery)
一旦需要修改,我们希望只在系统的一个地方进行修改,否则,就属于两种非常相似的坏味道的一种:如果某个类经常因为不同的原因在不同的地方发生变化,那么Divergent Change就出现了;如果系统每遇到一个小变化,就需要在多个不同的类内进行许多小修改,这属于Shotgun Surgery。Divergent Change是指“一个类受多种变化的影响”,Shotgun Surgery则指的是“一种变化引发多个类的修改”。 -
平行继承体系(Parallel Inheritance Hierarchies)
这其实是Shotgun Surgery的特殊情况——每当你为某个类添加一个子类,你也必须为它的兄弟类加一个子类。如果你发现某个继承体系的类名称前缀和另一个继承体系的类名称前缀完全相同,就属于这种情况。 -
依恋情结(Feature Envy)
面向对象的精髓在于:“将数据和对数据的操作行为包装在一起”。有一种气味是:函数对某个类的兴趣高过自己所处的类的兴趣。有很多时候,我们看到一个函数为了计算某个值,从另一个对象那儿调用了几乎半打的取值函数。最根本的原则是:将总是一起变化的东西放在一起。 -
数据泥团(Data Clumps)
你常常可以在很多地方看到相同的三四项数据:两个类中相同的字段、许多函数签名中相同的参数。这些绑在一起出现的数据真应该拥有属于它们自己的对象。减少字段和参数的个数,当然可以去除一些坏味道,但更重要的是:一旦拥有新对象,你就有机会寻找Feature Envy,这可以帮你指出能够移至新类中的种种方法。 -
基本类型偏执(Primitive Obsession)
对象技术的新手通常不愿意在小任务上运用对象——例如结合数值和币种的Money类、由一个起始值和一个结束值构成的Range类、电话号码或邮政号码等的特殊字符串。 -
switch语句(Switch Statements)
从本质上讲,switch语句的问题在于重复,面向对象的多态特性可以优雅地解决这个问题。如果你只是在单一函数内有些选择事例,那么用多态就属于杀鸡用牛刀了,这种情况下Replace Parameter with Explicit Methods是个不错的选择,如果你的选择之一是null,记得使用Introduce Null Object。 -
多余的类(Lazy Class)
你所创建的每一个类,都得有人去理解它、维护它,这些工作都是要花钱的。如果一个类的所得不值得其身价,就应该消除这个类。 -
过度设计(Speculative Generality)
当有人说“噢,我想我们有一天需要做这个事情”,并因此而企图以各种各样的钩子和特殊情况来处理一些非必要的事情,这种坏味道就出现了。软件设计不可过度设计,否则会使得系统难以理解和维护。 -
令人迷惑的暂时字段(Temporary Field)
有时你会看到这样的现象:类内的某个实例变量仅为某种特定情况而设。这样的代码不易理解,因为通常认为对象在所有时候都需要它的所有变量。 -
过度耦合的消息链(Message Chains)
如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象……这就是消息链。实际代码中你看到的可能是一长串getXXX()调用,意味着客户代码将与查找目标对象过程中的导航结构紧密耦合,一旦对象间的关系发生任何变化,客户端就会受到影响。 -
中间人(Middle Man)
对象的基本特征之一是封装——对外部世界隐藏其内部细节。封装往往伴随着委托,比如你问主管是否有时间参加一个会议,他就把这个消息“委托”给他的记事簿,然后才能回答你——你没有必要这位主管到底是使用传统记事簿或电子记事簿或秘书来记录自己的约会。但是,不要过度使用委托——你也许会看到某个类有一半接口都委托给其他类。 -
狎昵关系(Inappropriate Intimacy)
类与类之间过分紧密的关系必须拆散——可以引入第三方类或者利用委托。 -
异曲同工的类(Alternative Classes with Different Interfaces)
如果两个函数做同一件事,却有着不同的签名,请运用Rename Method根据它们的用途重新命名。但这往往不够,请反复运用Move Method将某些行为移入类,知道这两个函数的协议一致为止。如果你必须移动大量代码才可以完成这个工作,那还不如直接构建一个父类。 -
不完美的库类(Incomplete Library Class)
复用常常被认为是面向对象技术的终极目标。很多第三方库提供的接口经常不能恰如其分得满足我们的需求,这时候就需要对第三方接口做一层转换,或者给它添加一定的行为。 -
数据类(Data Class)
所谓Data Class,指的是:这种类拥有一些字段,以及用于访问(读、写)的函数,除此之外啥都没有。这样的类只是一种不会说话的数据容器,它们一定被其他类过分细碎得控制着。
Data Class就像小孩子,作为一个起点很好,但若要让它们像成熟的对象那样参与整个系统的工作,它们就必须承担一定责任。但是,在Spring框架开发中,我们经常需要定义很多domain对象。 -
被拒绝的遗嘱(Refused Request)
子类应该继承超类的函数和数据,但如果它们不想或者不需要继承,又该怎么办呢?按照传统说法,这就意味着继承体系的设计错误。你需要为这个子类新建一个兄弟类,然后让父类只包括两个子类共享的部分。
一般而言,这就足够了,但是如果子类不愿意支持超类提供的接口,则说明不能使用继承处理,应该使用委托。 -
过多的注释(Comments)
常常会有这样的情况:你看到一段代码有着长长的注释,然后发现,这些注释之所以存在乃是因为代码很糟糕。当你需要些注释时,要先尝试重构下代码,争取让代码拥有自说明性。