代码的坏味道
1、Duplicated Code(重复代码)
1)同一个类的两个函数含有相同的表达式
提炼重复的代码,然后让两个函数都调用被提炼出来的那一段代码。
2)两个互为兄弟的子类内含有相同的表达式
对两个类都进行代码提炼,然后将被提炼出来的代码中相似部分和差异部分分割开,构成单独一个函数。然后你可能发现可以运用From Template Method(345)获得一个Template Method设计模式。
如果有些函数以不同的算法做相同的事,你可以选择较清晰的一个,并使用Substitude Algorithm将其他函数的算法替换掉。
3)两个毫不相干的类出现Duplicated Code
应该考虑对其中一个类提炼代码,将重复代码提炼到一个独立类中,然后在另一个类中使用这个新类;重复代码也可能应该属于某个类,那么其他类应该引用这个类。
2、Long Method(过长函数)
通过不停地将代码中具有具体用途的部分提炼出来,可以避免函数过长。遵循一条原则:每当感觉需要写注释来说明点什么的时候,我们就需要说明的东西写进一个独立的函数中,并以其用途(而非实现手法)命名。
1)注释。注释通常能指出代码用途和实现手法之间的语义距离,如果代码前方有一行注释,就可以将这段代码替换成函数。
2)条件表达式和循环。可以使用Decompose Conditional(238)处理条件表达式;应该将循环内的代码提炼到一个独立的函数中。
3、Large Class(过大的类)
如果单个类做太多事,往往就会出现太多实例变量,也很容易出现重复代码。
1)提炼子类
提炼时选择相关的变量、方法,组合成新类。通常如果类内的数个变量有相同的前缀或字尾,意味着有机会提炼到某个组件内。
2)提炼函数
如果类中的函数过长,它们之中很多代码相同,那可以把它们提炼成多个的短函数。
3)提炼接口
确定客户端如果使用类中代码,为每一种使用方式提炼出一个借口。这可以帮助看清楚如何分割这个类。
4、Long Parameter List(过长参数列)
坏味道:
1)难以理解,需要识别各个参数的含义
2)太多参数很容易造成前后不一致,传递参数出错
3)如果需求变更,需要更多数据,就不得不修改参数
解决办法:
1)将函数作为行为提炼到归属的类中,使用类属性替代参数传递
2)将多个参数归类,以对象的形式传递给函数
5、Divergent Change(发散式变化)
坏味道:
如果某个类经常因为不同的原因,需要修改的地方很多,比如 新增一个数据库、添加一个功能,需要修改多个函数。
解决办法:
可以将类提炼拆分成不同的类,针对某一变化的时候,相应修改只发生在一个类中。
6、shotgun Surgery(霰弹式修改)
坏味道:
如果遇到某种变化,许多类都要做出许多小修改,很容易忘记某个重要修改。
解决办法:
把所有需要修改的代码放进同一个类。如果没有合适的类安置这些代码,就新建一个类。
7、Feature Envy(依恋情结)
坏味道:
函数对其它类的依赖性比宿主类还要高,从其它类调用几乎半打的取值函数。
解决办法:
将这个函数分解成数个较小函数,判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据放到一个类。
8、Data Clumps(数据泥团)
坏味道:
两个类中有相同的字段;许多函数签名中有相同的参数。
解决办法:
找出这些数据以字段出现的地方,运用Extract Class提炼到一个独立的对象中;函数签名上,运用Introduce Parameter Object或Preserve Whole Object为它减肥。这么做的直接好处就是可以将很多参数列缩短,简化函数调用。
9、Primitive Obsession(基本类型偏执)
对象的概念模糊(设置打破)了横亘于基本数据和体积较大的类之间的界限。
1)可以运用Replace Data Value with Object(175)将原本单独存在的数据值替换为对象
2)如果想要替换类型码,而不影响行为,可以运用Replace Type Code with Class(218)将它换掉
3)如果有与类型码相关的条件表达式,可以运用Replace Type Code with Subclass(213)或Replace Type Code with State/Strategy(227)加以处理
10、Switch Statements(switch惊悚现身)
坏味道:
代码中switch语句散布于不同地方,如果要为它添加一个新的case子句,就必须找到所有switch语句并修改它们。
解决办法:
1)使用Extract Method将switch语句提炼到一个独立函数中,再以Move Method(142)将它搬移到需要多态性的类中。
2)如果只是在单一函数中有些选择实例,且并不想改动它们,可以运用Replace Parameter with Explicit Methods(285);如果选择条件之一是null,可以运用Introduce Null Object(260)
11、Temporary Field(令人迷惑的暂时字段)
坏味道:
类中某个实例变量仅为某种特定情况而设,这样的代码让人不易理解,因为通常认为对象在所有情况都需要它的所有变量。这种不常用的临时字段,需要猜测当初其设置目的。
解决办法:
1)使用Extract Class创建一个类,把所有和这个变量相关的代码放进这个类中。
2)如果类中有一个复杂算法,需要好几个变量,由于不希望传递以长串参数,所以把这些参数都放进字段中。但是这些字段只在使用该算法时才有效,其它情况容易让人迷惑,这时候可以与用Extract Class把这些变量和其相关函数提炼到一个独立类中。
12、Message Chains(过度耦合的消息链)
坏味道:
代码中使用一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象……实际代码中看到的可能是一长串getThis()或一长串临时变量。这种方式意味客户端代码将与查找过程中的导航结构紧密耦合,一旦对象间关系发生任何变化,客户端就不得不修改。
解决办法:
先观察消息链最终得到的对象是用来干什么的,看能否以Extract Method把使用该对象的代码提炼到一个独立函数,在运用Move Method把这个函数推入消息链。
13、Inappropriate Intimacy(狎昵关系)
坏味道:
两个类之间互相调用对方的private成员,比如继承往往带来这种依赖关系。
解决办法:
1)可以采用Move Method和Move Field,减少降低它们的依赖耦合程度
2)如果两个类确实依赖性较强,可以运用Extract Class把两者共同点提炼到一个新类;或者运用Hide Delegate(157)让另一个类处理它们之间的关联行为
14、Comments(过多的注释)
坏味道:
代码中存在一些长注释,是为了解释一段难以理解的糟糕代码。
处理办法:
把被注释的代码进行重构,使代码结构清晰容易理解。
1)如果注释是用来解释代码做了什么,运用Extract Method把代码块提炼成函数
2)如果函数已经提炼出来,还需要注释来解释其行为,运用Rename Method使方法名称很易理解其行为
3)如果需要注释说明某些系统的需求规格,运用Introduce Assertion引入断言。