首页 > 代码库 > 重构笔记——代码的坏味道(上)

重构笔记——代码的坏味道(上)

  在重构入门篇中,简单地介绍了重构的定义、为何重构、何时重构等。我想对于重构是如何运作的,你已经有了较好的理解了。但是对于代码中的坏味道,你可能 知道的并不多。坏味道可能是无形中产生的,也可能是开发人员偷懒造成的,还可能是其它某些因素导致的。不管怎么样,代码中的坏味道对程序没有半点好处,它 会促使程序腐烂,甚至变质。对于开发人员,真的是很有必要对这些坏味道进行了解和熟悉,理解它们产生的场景。针对当前程序,发现坏味道,对代码进行重构, 以消除坏味道,提高代码质量,提高自己水平。

       

       下面让我们一起来熟悉和学习开发中经常出现的22种坏味道情形中的11种。其中,前四种坏味道在大多数开发过程中是不可避免的情形;后面若干种坏味道也会经常性地出现在程序中。我们应该发现它们的存在,并消除它们。


(1)重复的代码

       重复的代码是坏味道中出现频率最高的情形非其莫属。如果在一个的以上地方看到相同的代码,那么就可以肯定:想办法将它们合而为一,代码会变得更好。最单纯的重复代码就是“同一个类的两个函数含有相同的表达式”,这时候可以采用抽取方法提炼出重复的代码,然后让这两个地点都调用被提炼出的那一段代码。

       另一种常见情况就是“两个互为兄弟的子类内含相同的表达式”,这时候只需对两个类抽取方法,然后将提炼出的代码推入到超类中。如果代码之间只是类似而并非完全相同,那么就需要通过抽取方法将相似部分和差异部分分开,构成单独一个函数。如果有些函数以不同的算法做相同的事,可以使用比较清晰的一个替换掉其余的。


(2)过长的函数

       程序员都喜欢简短的函数。拥有短函数的对象会活的比较好、比较长。不熟悉面向对象技术的人,常常觉得对象程序中只有无穷无尽的委托,根本没有进行任何计算。和此类程序共同生活数年后,你才会知道这些小小函数的价值。

       应该积极地分解函数,将长长的函数变为多个短小的函数。一般会遵循这样的原则:每当感觉需要用注释来说明点什么的时候,就把需要说明的东西写进一个独立函数中,并以其用途命名。不要嫌麻烦。可以对一组甚至短短一行代码做这件事,哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,也应毫不犹豫地那么做。关键不在于函数的长度,而在于函数“做什么”和“如何做”之间的语义距离。       


(3)过大的类

       如果想利用单个的类做太多的事情,其内往往会出现太多实例变量。一旦如此,重复的代码就接踵而来。

       可以将几个变量一起提炼至新类内。提炼时应该选择类内彼此相关的变量,将它们放在一起。通常如果类内的数个变量有着相同的前缀或字尾,这就意味有机会把它们提炼到某个组件内。

       和“太多实例变量”一样,类内如果有太多代码,也是代码重复、混乱并最终走向死亡的源头。最简单的方案是把多余的东西消弭于类内部。如果有五个“百行函数”,它们之中很多代码都相同,那么或许你可以把它们变成五个“十行函数”和十个提炼出的“双行函数”。


(4)过长的参数列

       刚开始学编程的时候,或许都是“把函数所需的所有东西都以参数传递进去”。这样也是可以理解的,因为除此之外就只能选择全局数据,而全局数据是邪恶的东西。对象技术告诉我们,如果你手上没有所需的东西,总可以叫一个对象给你。有了对象,你就不必要把函数所需的所有东西都以参数传递给它,只需传给它足够的、让函数能从中获得自己的东西就行。

       太长的的参数列难以理解,太多参数会造成前后不一致、不易使用,而且一旦需要更多数据,就不得不修改它。如果将对象传递给函数,大多数修改都将没有必要,因为很可能只需增加一两条请求,就能得到更多的数据。


(5)发散式变化

       我们希望软件能够容易被修改——毕竟软件再怎么说本来就该是“软”的。一旦需要修改,我们希望能够跳到系统某一点,只在该处做修改。如果不能做到这点,你就会嗅出两种紧密相关的刺鼻味道中的一种。

       如果某个类经常因为不同的原因在不同的方向上发生变化,发散式变化就出现了。其主要指“一个类受多种变化的影响”。当你看着一个类说:“呃,如果新加入一个数据库,就必须修改这三个函数;如果新出现一种工具,就必须修改这四个函数。”那么此时也许将这个对象分成两个会更好,这样对每个对象就可以只因一种变化而需要修改。


(6)霾弹式修改

       如果每遇到变化,都必须在许多不同的类内做出许多小修改,你所面临的坏味道就是霾弹式修改。其主要指“一种变化引发多个类相应修改”。如果需要修改的代码散布四周,不但很难找到它们,也很容易忘记某个重要的修改。

       这种情况可以把所有需要的代码放进同一个类。如果眼下没有合适的类可以安置这些代码,就创造一个。通常可以运用内联类把一系列相关行为放进同一个类。


(7)依恋情节

       众所周知,对象技术的全部要点在于:其是一种“将数据和对数据的操作行为包装在一起”的技术。有一种经典的气味:函数对于某个类的兴趣高过对自己所处类的兴趣。在很多情况下,都能够看到:某个函数为了计算某个值,从另一个对象那儿调用几乎半打的取值函数。疗法也显而易见:把这个函数移至另一个地点,移到它该去的地方。‘

       有时候一个函数往往会用到几个类的功能,那么它究竟该被置于何处呢?处理原则通常为:判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据摆在一起。


(8)数据泥团

       如果用比较形象的事物来形容数据项,我想“小孩子”是一个不错的选择,数据项就像小孩子,喜欢成群结队地呆在一块儿。常常可以在很多地方看到相同的三四项数据:两个类中相同的字段、许多函数签名中相同的参数。这些总是绑在一起出现的数据真应该拥有属于它们自己的对象。

       这种情况可以先找出这些数据以字段形式出现的地方,将它们提炼到一个独立对象中,然后将注意力转移到函数签名上,运用参数对象为它减肥。这样做的直接好处是可以将很多参数列缩短,简化函数调用。一个比较好的评判方法是:删掉众多数据中的一项。这么做其它数据有没有因而失去意义?如果它们不再有意义,这就是一个明确的信号:应该为它们产生一个新对象。


(9)基本类型偏执

       大多数编程环境都有两种数据:结构类型允许你将数据组织成有意义的形式;基本类型则是构成结构类型的积木块。但是请记住:结构总是会带来一定的额外开销。它们可能代表着数据库中的表,如果只为做一两件事而创建结构类型也可能显得很麻烦。

       对象的一个极大价值在于:它们模糊甚至打破横亘于基本数据和体积较大的类之间的界限。如果你有一组应该总是被放在一起的字段,可以将其抽取为一个独立的类。如果你在参数列中看到基本型数据,可以引入参数对象进行处理。如果你发现自己正从数组中挑选数据,可以运用以对象取代数组进行处理。


(10)Switch惊悚现身

       面向对象程序的一个较明显特征是:少用switch语句。从本质上说,switch语句的问题在于重复。你常会发现同样的switch语句散布于不同的地方。如果要为它添加一个新的case语句,就必须找到所用switch语句并修改它们。面向对象中的多态概念可为此带来优雅的解决办法。

       大多数时候,一看到switch语句,那就应该考虑以多态来替换它。switch语句常常根据类型码进行选择,你要的是“与该类型码相关的函数或类”,所以应该将switch语句提炼到一个独立函数中,再将它搬移到需要多态性的那个类里。


(11)平行继承体系

       平行继承体系其实是霾弹式修改的特殊情况。在这种情况下,每当为某个类增加一个子类,必须也为另一个类增加一个子类。如果发现某个继承体系的类名称前缀和另一个继承体系的类名称前缀完全相同,这种坏味道就会被嗅出。

       消除这种重复性的一般策略是:让一个继承体系的实例引用另一个继承体系的实例。


       本文简单介绍了22种代码坏味道中的11种,通过对这些坏味道的了解,促使我们在开发过程中,经常性地思考自己所写代码中是否存有坏味道,一旦发现坏味道,就应该着手进行重构,而不是看着代码慢慢腐烂。如果你能坚持完整地看完本文,我想你肯定也会坚持经常性地清理代码中坏味道,提高代码的质量,提高自己的水平。

重构笔记——代码的坏味道(上)