重构之代码的坏味道

来源:互联网 发布:java视频教程毕向东 编辑:程序博客网 时间:2024/05/16 00:36
一.重复代码
坏代码行列中首当其冲的就是重复代码,如果你在一个以上的地点看到相同的程序结构,那么可以肯定,设法将它们合而为一,程序会变得更好。
最单纯的重复代码就是"同一个类的两个函数含有相同的表达式"。这时候你需要做的就是采用提炼函数提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。
另一个常见情况就是"两个互为兄弟的子类内含有相同表达式"。要避免这种情况,只需要对两个类都使用提炼函数,然后再对被提炼出来的代码使用函数上移,将它推入超类内。如果代码只是相似,并非完全相同,那么就得运用提炼函数将相似部分和差异部分分割开,构成单独一个函数。然后你可能发现可以运用塑造模板函数获得一个模板模式。
二.过长函数
"程序越长越难理解",我们应该更积极地分解函数。我们遵循这样一条原则:每当感受需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并一起用途命名。我们可以对一组甚至短短一行代码做这件事。哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,我们也该毫不犹豫地那么做。
三.过大的类
如果想利用单个类做太多事情,其内往往就会出现太多实例变量。一旦如此,重复代码也就接踵而至了。和"太多实例变量"一样,类内如果有太多代码,也是代码重复,混乱并最终走向死亡的源头。最简单的解决方案是把多余的东西小米与类内部。如果有五个百行函数,他们之中很多代码都相同,那么或许可以把它们变成五个十行函数和十个提炼出来的双行函数。
四.过长参数列
有了对象,就不必把函数需要的所有东西都以参数传递给它了,只需传给它足够的,让函数能从中获得自己需要的东西就行了。太长的参数列难以理解,太多参数会造成前后不一致,不易使用,而且一旦你需要更多数据,就不得不修改它。如果将对象传递给参数,大多数修改都将没有必要,很可能只需增加一两条请求,就能得到更多数据。
五.发散式变化
如果某个类经常因为不同的原因在不同的方向上发生变化,发散式变化就出现了。当你看着一个类说,如果新加入一个数据库,我必须修改这三个函数;如果新出现一种金融工具,我必须修改这四个函数。那么此时也许将这个对象分成两个会更好,这么一来每个对象就可以指因一种变化而需要修改。这也是面向对象原则中的单一职责原则。
六.散弹式修改
如果每遇到某种变化,都必须在许多不同的类内做许多小修改,这个就是散弹式修改。如果需要修改的代码散布四处,不但很难找到他们,也很容易忘记某个重要的修改。这种情况下应该使用搬移函数和搬移字段把所有需要修改的代码放进同一个类。如果眼下没有合适的类可以安置这些代码,就创造一个。
七.依恋清洁
对象技术的全部要点在于:这是一种将数据和对数据的操作行为包装在一起的技术,有一种经典气味是:函数对某个类的兴趣高过对自己所处类的兴趣。无数次经验里,我们看到某个函数为了计算某个值,将另一个对象那儿调用几乎半大的取值函数。疗法显而易见:把这个函数一直另一个地点。
当然,并非所有情况都这么简单。一个函数往往会用到几个类的功能,那么它究竟应该被置于何处呢?我们的原则是:判断哪个类拥有最多被此函数使用的数据,然后把这个函数和那些数据摆在一起。
八.数据泥团
常常可以在很多地方看到相同的三四项数据:两个类中相同的字段,许多函数签名中相同的参数。这些总是绑在一起出现的数据真应该拥有属于它们自己的对象。首先请找出这些数据以字段形式出现的地方,运用提炼类将它们提炼到一个独立对象中。这么做的直接好处是可以将很多参数列缩短,简化函数调用。一个好的评判方法是:删掉众多数据中的一项,这么做其他数据有没有因而是去意义,如果他们不再有意义,那么应该为他们产生一个新对象。
九.基本类型偏执
大多数编程环境都有两种数据:结构类型允许你将数据组织成有意义的形式;基本类型则是构成结构类型的积木块。对象的一个极大的价值在于:它们模糊了横亘与基本数据和体积较大的类之间的界限。
如果你有一组应该总是被放在一起的字段,可运用提炼类。如果你在参数列中看到基本行数据,不妨试试引入参数对象。如果你发现自己正从数组中挑选数据,可运用以对象取代数组。
十.switch惊悚现身
面向对象程序的一个最明显特征就是:少用switch语句。从本质上来说,switch语句的问题在于重复。你常会发现同样的switch语句散布于不同地点。如果要为它添加一个新的case子句,就必须找到所有switch语句并修改它们。面向对象中的多态概念可谓词带来优雅的解决办法。大多数时候,一看到switch语句,就应该考虑以多态来替换它。
十一.平行继承体系
平行继承体系意味着,每当腻味某个类增加一个子类,必须也为另一个类相应地增加一个子类。如果你发现某个继承体系的类名称前缀和另一个集成体系的类名称前缀完全相同,便是闻到了这种坏味道。消除这种重复性的一般策略是:让一个继承体系的实例引用另一个继承体系的实例。
十二.冗赘类
你所创建的每一个类,都得有人去理解它,维护它,这些工作都是要花钱的。如果一个类的所得不值其身价,它就应该消失。项目中经常会出现这样的情况:某个类原本对得起自己的身价,但重构使他身形缩水,不再做那么多工资:或开发者事前规划了某些变化,并添加一个类来应付这些变化,但变化实际上并没有发生。不论什么原因,就让这个类庄严赴义吧。
十三.夸夸其谈未来性
当有人说:我想我们总有一天需要做这事,并因而其他以各种各样的钩子和特殊情况来处理一些非必要的事情,这种坏味道就出现了。那么做的结果往往造成系统更难理解和维护。如果所有装置都会被用到,那么值得那么做;如果用不到就不值得。用不上的装置只会挡你的路,所以把它搬开吧。
十四.令人迷惑的暂时字段
有时你会看到这样的对象:其内某个实例变量仅为某种特殊情况而设。这样的代码让人不易理解,因为你通常认为对象在所有时候都需要它的所有变量。在变量未被使用的情况下猜测当初其设置目的,会让你发疯的。
如果类中有一个复杂算法,需要好几个变量,往往就可能导致坏味道令人迷惑的暂时字段出现。由于实现者不希望传递一长串参数,所以他把这些参数都放进字段中。但是这些字段只在使用该算法时才有效,其他情况下只会让人迷惑。这时候你可以利用提炼类把这些变量和其他相关函数提炼到一个独立类中。
十五.过度耦合的消息链
如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象......这就是消息链。实际代码中你看到的可能是一长串getThis()或一长串临时变量。采取这种方式,意味客户代码将在查找过程中的导航结构紧密耦合。一旦对象间的关系发生任何变化,客户端就不得不作出相应修改。
这时候你应该使用隐藏委托关系。你可以在消息链的不同位置进行这种重构手法。理论上可以重构消息链上的任何一个对象,但这么做往往会把一系列对象都变成中间人。通常更好的选择是:先观察消息链最终得到的对象是用来干什么的,看看能否以提炼函数把使用该对象的代码提炼到一个独立函数中,再运用移动方法吧这个函数推入消息链。
十六.中间人
对象的基本特征之一就是封装-对外部世界隐藏其内部细节。封装往往伴随委托。比如说你问你主管是否有时间参加一个会议,他就把这个消息委托给他的记事本,然后才能回答你,你没必要知道这位主管到底使用传统记事本还是电子记事本。
但是人们可能过度运用委托。你也许会看到某个类接口有一半的函数都委托给其他类,这样就是过度运用。这时应该使用移除中间人,直接和真正负责的对象打交道。如果这样不干实事的函数只有少数几个,可以运用内联函数(后续介绍)把它们放进调用端。如果这些中间人还有其他行为,可以运用以继承取代委托把它变成实责对象的子类,这样你既可以扩展原对象的行为,又不必负担那么多的委托动作。
十七.狎昵关系
有时你会看到两个类过于亲密,花费太多时间去探究彼此的private成分。过分狎昵的类必须拆散。可以采用搬移函数和搬移字段帮它们划清界限,从而减少狎昵行径。也可以看看是否可以运用将引用对象改为值对象让其中一个类对另一个斩断情丝。也可以运用提炼类把两者共同点提炼到一个安全地点,或者可以尝试运用隐藏委托关系让另一个类来为他们传递相思情。继承往往造成过度亲密,因为子类对超类的了解总是超过后者的主观愿望。
十八.异曲同工的类
如果两个函数做同一件事,却有着不同的签名,请运用函数改名根据他们的用途重新命名。但这往往不够,请反复运用移动函数将某些行为移入类,直到两者的协议一致为止。
十九.不完美的类库
复用常被视为对象的终极目的。不过我们认为,复用的意义经常被高估——大多数对象只要够用就好。但是不可否认,许多编程技术都建立在程序库的基础上。类库构筑者没有未仆先知的能力,我们不能因此责怪他们。麻烦的是库往往构造的不够好,而且往往不可能让我们修改其中的类使它完成我们希望完成的工作。如果你只想修改库类的一两个函数,可以运用引入外加函数。如果想要添加一大堆额外行为,就得运用引入本地扩展。
二十.纯稚的数据类
所谓数据类是指:他们拥有一些字段,以及用于访问这些字段的函数,除此之外一无长物。这样的类只是一种不会说话的数据容器,他们几乎一定被其他类过分细锁地操控着。这些类早起可能拥有public字段,果真如此你应该在别人注意到他们之前,立刻运用封装字段将它们封装起来,如果这些类内含容器类的字段,你应该检查它们是不是得到了恰当的封装,如果没有,就运用封装容器把它们封装起来。
二十一.被拒绝的遗赠
子类应该继承超类的函数和数据。但如果它们不想或不需要继承,又该怎么办呢?按传统说法,这就意味着继承体系设计错误。你需要为这个子类新建一个兄弟类,再运用函数下移和字段下移把所有用不到的函数下推给那个兄弟。这样依赖,超类就只持有所有子类共享的东西。你常常会听到这样的建议:所有超类都应该是抽象的。我们不建议传统说法这么做,起码不建议每次都这么做,我们经常利用继承来复用一些行为,并发现这可以很好地应用于日常工作。这也是一种坏味道,我们不否认,但气味通常并不强烈。所以我们说,如果Refused Bequest引起困惑和问题,请遵循传统忠告,但不必每次都得这么做,十有八九这种坏味道很淡,不值得理睬。如果子类复用了超类的行为,却又不愿意支持超类的接口,Refused Bequest的坏味道就会变得强烈。拒绝继承超类的实现,这一点我们不介意,但如果拒绝继承超类的接口,我们不以为然,不过即使你不愿意继承接口,也不要胡乱修改继承体系,应该运用以委托取代继承来达到目的。
二十二.过多的注释
我们并不是说你不该写注释。事实上过多的注释不是一种坏味道,还是一种香味呢,之所以提到过多的注释,是因为人们常把它当作除臭剂来使用。常常有这样的情况:你会看到一段代码有着长长的注释,然后发现这些注释之所以存在是因为代码很糟糕。过多的注释可以带我们找到先前提到的各种坏味道。找到坏味道后,我们首先应该以各种重构手法把坏味道去除。完成之后我们发现,只是已经变得多余了,因为 代码已经清楚说明了一切。如果你需要注释来解释一块代码做了什么,试试提炼函数,如果函数已经提炼出来,但还是需要注释来解释其行为,试试重命名方法,如果需要注释说明某些系统的需求规格,试试引入断言。
如果你不知道该做什么,这才是注释的良好运用时机。除了用来记述将来的打算之外,注释还可以用来标记并无十足把握的区域。可以在注释里写下自己为什么做某某事,这类信息可以帮助将来的修改者。

文章内容来自重构 改善既有代码的设计一书,读了两章觉得很不错,接下来会记录一些笔记。
原创粉丝点击