代码的坏味道
1、Duplicated Code重复代码
2、Long Method过长函数
如果函数内有大量的参数和临时变量,它们会对你的函数提炼形成阻碍。如果你尝试运用Extract Method(提炼函数),最终就会把许多参数和临时变量当做参数,传递给提炼出来的新函数,导致可读性机会没有任何提升。此时,你可以经常运用Replace Temp with Query(查询取代临时变量)来消除这些临时元素。IntroDuce Parameter Object(引入参数对象)和Preserve Whole Object(保持对象完整)则可以将过长的参数列变得更简洁一些。
如果你已经这样做了,仍然有太多临时变量和参数,那就应该使出我们的杀手锏:Replace Method with Method Object(函数对象取代函数)。
如果确定该提炼哪一段代码呢?一个很好的技巧是:寻找注释。它们通常能指出代码用途和实现手法之间的语义距离。如果代码前方有一行注释,就是在提醒你:可以将这段代码替换成一个函数,而且可以再注释的基础上给这个函数命名。就算只有一行代码,如果它需要以注释来说明,那就值得将它提炼到独立函数去。
条件表达式和循环常常也是提炼的信号。你可以使用Decompose Conditional(分解条件表达式)处理条件表达式。至于循环,你应该将循环和其内的代码提炼到一个独立函数中。
3、Large Class过大的类
如果想利用单个类做太多事情,其内往往会出现太多实例变量。一旦如此,Duplicated Code也就接踵而来。
你可以运用Extract Class(提炼类)将几个变量一起提炼到新类内。
和“拥有太多实例变量”一样,一个类如果拥有太多代码,往往也适合使用Extract Class(提炼类)和Extract Subclass(提炼子类)。这里有个技巧,想确定客户端如何使用它们,然后运用Extract Interface(提炼接口)为每一种使用方式提炼出一个接口。这或许可以帮助你看清楚如何分解这个类。
4、Long Parameter List(过长参数列)
5、Divergent Change(发散式变化)
如果某个经常因为不同的原因在不同的方向上发生变化,Divergent Change就出现了。当你看着一个类说:“呃,如果新加入一个数据库,我必须修改这三个函数:如果新出现一个金融工具,我必须修改这四个函数。”那么此时也许将这个对象分成两个会更好,这么一来每个对象就可以只因为一种变化而需要修改。
6、Shotgun Surgery(雾弹式修改)
与Divergent Change(发散式变化)恰恰相反。如果每遇到某种变化,你都必须在许多不同的类内做出许多小修改,你所面临的坏味道就是Shotgun Surgery(雾弹式修改)。
这个情况下你应该使用Move Method(搬移函数)和Move Field(搬移字段)把所有需要修改代码放进同一个类。
Divergent Change(发散式变化)是指“一个类受多种变化的影响”,这时把这个类拆分成多个类,Shotgun Surgery(雾弹式修改)则是指“一种变化引发多个类相应修改“,这时把所有需要修改代码放进同一个类。
7、Feature Envy(依恋情结)
对象技术的全部要点在于:将数据和对数据的操作行为包装在一起的技术。
有一种经典气味是:函数对某个类的兴趣高过于对自己所处类的兴趣。这种孺慕之情最通常的焦点便是数据。你应该使用Move Method(搬移函数)把它移到它该去的地方。有时候函数中只有一部分受这种依恋之苦,这时候你应该使用Extract Method(提炼函数)把这一部分提炼到函数中,再使用Move Method(搬移函数)带它去它的梦中家园。
当然,并非所有情况都这么简单。一个函数往往会用到几个类的功能,那么它究竟该被置于何处呢?我们的原则是:判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据摆在一起。如果先以Extract Method(提炼函数)将这个函数分解成数个较小函数并分别置放于不同地点,上述步骤也就比较容易完成了。
8、Data Clumps(数据泥团)
你常常可以看到相同的三四项数据:两个类中相同的字段、许多函数签名中相同的参数。这些总是绑定在一起出现的数据真应该拥有属于它们自己的对象。首先请找出这些数据以字段形式出现的地方,运用Extract Class(提炼对象)将它们提炼到一个独立对象中。
9、Primitive Obsession(基本类型偏执)
对象技术的新手通常不愿意在小任务运用小对象——像是结合数值和币种的Money类、由一个起始值和一个结束值组成的Range类、电话号码或邮政编号(ZIP)等等特殊字符串。你可以运用Replace Data Value with Object(以对象替换数据值)将原本单独存在的数据值替换为对象,从而走出传统的洞窟,进入炙手可热的对象世界。如果想要替换的数据值是类型码,而它并不影响行为,则可以运用Replace Type Code with Class(以对象替换类型码)将它换掉。如果你有与类型码相关的条件表达式,可运用Replace Type Code with Subclass(以子类替换类型码)或Replace Type Code with State/Strategy(以State/Strategy替换类型码)加以处理。
10、Switch Statements(switch 惊悚现身)
面向对象程序的一个最明显特性就是:少用switch(或case)语句。从本质上说,switch语句的问题在于重复。面向对象中的多态概念可为此带来优雅的解决方法。
11、Parallel Inheritance Hierarchies(平行继承体系)---暂时不是很懂
Parallel Inheritance Hierarchies(平行继承体系)其实是Shotgun Surgery(雾弹式修改)的特殊情况。在这种情况下,每当你为某个类增加一个子类,必须也为另一个类相应增加一个子类。如果你发现某个继承体系的类名称前缀和另一个继承体系的类名称前缀完全相同,便是闻到了这种坏味道。
消除这种重复性的一般策略是:让一个继承体系的实例引用另一个继承体系的实例。如果再接再厉运用Move Method(搬移函数)和Move Field(搬移字段),就可以将引用段的继承体系消弭于无形。
12、Lazy Class(冗赘类)
13、Speculative Generality(夸夸其谈未来性)
14、Temporary Field(令人迷惑的暂时字段)
15、Message Chains(过度耦合的消息链)
如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象.........这就是消息链。
16、Middle Man(中间人)
17、Inappropriate Intimacy(狎昵关系)
两个类过于亲密
18、Alternative Classes with Different Interfaces (异曲同工的类)
19、Incomplete Library Class(不完美的库类)
20、Data Class(纯稚的数据类)
21、Refused Bequest(被拒绝的遗赠)
子类应该继承超类的函数和数据。但如果它们不想或不需要继承,又该怎么办呢?它们得到所有礼物,却只从中挑选几样来玩!
按传统说法,这就意味着继承体系设计错误。你需要为这个子类新建一个兄弟类,再运用Push Down Method(函数下移)和Push Down Field(字段下移)把所有用不到的函数下推给那个兄弟。
22、Comments(过多的注释)
如果你需要注释来解释一块代码做了什么,试试Extract Method(提炼函数);如果函数已经提炼出来,但还是需要注释来解释其行为,试试Rename Method(重命名函数);如果你需要注释说明某些系统的需求规格,试试Introduce Assertion(引入断言)。