转载

2.2 代码的坏味道(下)

2.11 Parallel Inheritance hierarchies (平行继承体系)

Parallel Inheritance Hierarchies其实是Shotgun Surgery 的特殊情况 。在这种情况下,每当你 为某个类增加一个子类,必须也为另一个类相应增加一个子类 。如果你发现某个继承体系的类名称前缀和另一个继承体系的类名称前缀完全相同,便是闻到了这种坏味道 。

消除这种重复性的一般策略是:让一个继承体系的实例引用另一个继承体系的实例。如果再接再励运用 Move Method和Move Field ,就可以将引用端的继承体系消灭于无形。

2.12 Lazy Class (冗赘类)

你所创建的每一个类,都得有人去理解它、维护它,这些工作都是要花钱的 。如果一个类的所得不值其身价,它就应该消失。项目中经常会出现这样的情况: 某个类原本对得起自己的身价,但重构使它身形缩水,不再做那么多工作;或开发者事前规划了某些变化,并添加一个类来应付这些变化,但变化实际上没有发生。不论上述哪一种原因,请让这个类庄严赴义吧。如果某些子类没有做足够的工作,试试 Collapse Hierarchy。对于几乎没用的组件,你应该以 Inline Class对付它们。

2.13 Speculative Generality (夸夸其谈未来性)

这个令我们十分敏感的坏味道,命名者是Brian Foote。当有人说"噢,我想我们 总有一天 需要做这事”,并因而企图以各式各样的钩子和特殊情况来处理一些非必要的事情,这种坏味道就出现了。那么做的结果往往造成系统更难理解和维护。如果所有装置都会被用到,那就值得那么做:如果用不到,就不值得。用不上的装置只会挡你的路,所以,把它搬开吧。

如果你的某个抽象类其实没有太大作用,请运用Collapse Hierarchy 。不必要的委托可运用Inline  Class 除掉。如果函数的某些参数未被用上,可对它实施Remove Parameter 。如果函数名称带有多余的抽象意味, 应该对它实施加Rename Method ,让它现实一些。如果函数或类的唯一用户是测试用例,这就飘出了坏味道 Speculative Generality 。

如果你发现这样的函数或类,请把它们连同其测试用例一并删掉。但如果它们的用途是帮助测试用例检测正当功能, 当然必须刀下留人。

2.14 Temporary Field (令人迷惑的临时字段)

有时你会看到这样的对象:其内某个实例变量仅为某种特定情况而设。这样的代码让人不易理解,因为你通常认为对象在所有时候都需要它的所有变量 .在变量未被使用的情况下猜测当初其设置目的 ,会让你发疯的 。

请使用Extract Class 给这个可怜的孤儿创造一个家,然后把所有利这个变量相关的代码都放进这个新家 .也许你还可以使用Introduce Null Object在"变量不合法 "的情况下创建一个Null对象,从而避免写出条件式代码。

如果类中有一个复杂算法 ,需要好几个变量,往往就可能导致坏味道Temporary Field的出现.由于实现者不希望传递一长串参数,所以他把这些参数都放进字段中.但是这些字段只在使用该算法时才有效, 其他情况下只会让人迷惑。这时候你可以利用 Extract Class 把这些变量和其相关函数提炼到一个独立类中。提炼后的新对象将是一个函做对象 [Beck].

2.15 Message Chains (过度耦合的消息链)

如果你看到用户向一个对象请求另一个对象 ,然后再向后者请求另一个对象,然后再请求另 一个对象……这就是 消息链 。实际代码中你看到的可能是一长串 getThis()或一长串临时变量.采取这种方式,意味客户代码将与查找过程中的导航结构紧密耦合.一旦对象间的关系发生任何变化,客户端就不得不做出相应修改。

这时候你应该使用Hide Delegate 。你可以在消息链的不同位置进行这种重构手法.理论上可以重构消息链上的任何一个对象,但这么做往往会把一系列对象都变成Middle Man。通常更好的选择是:先观察消息链最终得到的对象是用来干什么的,看看能否以Extract Method把使用该对象的代码提炼到一个独立函数中, 再运用Move Method 把这个函数推入消息链。如果这条链上的某个对象有多位客户打算航行此航线的剩余部分, 就加一个函数来做这件事.

有些人把任何函数链都视为坏东西,我们不这样想,具体情况具体分析。

2.16 Middle Man (中间人)

对象的基本特征之一就是封装,对外部世界隐藏其内部细节。封装往往伴随委托。比如说你问主管是否有时间参加一个会议, 他就把这个消息"委托"给他的记事簿 ,然后才能回答你。很好,你没必要知道这位主管到底使用传统记事簿或电子记事簿亦或秘书来记录自己的约会。

但是人们可能过度运用委托。你也许会看到某个类接口有一半的函数都委托给其他类 ,这样就是过度运用。这时应该使用Remove Middle Man . 直接和真正负责的对象打交道。如果这样"不干实事 " 的函数只有少数几个 ,可以运用 Inline Method把它们放进调用端。如果这些 Middle Man还有其他行为 ,可以运用Replace Delegation with lnheritance把它变成实责对象的子类,这样你既可以扩展原对象的行为 ,又不必负担那么多的委托动作。

2.17 Inappropriate Intimacy (狎昵xia,ni关系)

有时你会看到两个类过于亲密,花费太多时间去探究彼此的 private 成分。如果这发生在两个"人"之间,我们不必做卫道士:但对于类 ,我们希望它们严守清规。 就像古代恋人一样 ,过分狎昵的类必须拆散。你可以采用 Move Method 和Move Field 帮它们划清界线 ,从而减少狎昵行径。你也可以看看是否可以运用Change Bidirectional Association To Unidirectional让其中一个类对另一个斩断情丝。如果两个类实在是情投意合 ,可以运用Extract Class把两者共同点提炼到 个安全地点,让它们坦荡地使用这个新类。或者也可以尝试运用 Hide Delegate让另一 个类来为它们传递相思情 。

继承往往造成过度亲密,因为子类对超类的了解总是超过后者的主观愿望。 如果你觉得该让这个孩子独自生活了 ,请运用Replace Inheritance with Delegation 让它离开继承体系 。

2.18 Alternative Classes With Different Interfaces (异曲同工的类)

如果两个函数做同一件事,却有不同的签名,请运用Rename Method根据它们的用途重新命名。但这往往不够,请反复运用 Move Method将某些行为移入类,直到两者的协议一致为止。 如果你必须重复而赘余地移入代码才能完成这些,或许可运用Extract Superclass为自己赎点罪.

2.19 Incomplete Library Class(不完美的库类)

复用常被视为对象的终极目的.不过我们认为,复用的意义经常被高估,大多数对象只要够用就好。但是无可否认,许多编程技术都建立在程序库的基础上,没人敢说是不是我们都把排序算法忘得一干二净了 .

库类构筑者没有未卜先知的能力,我们不能因此责怪他们。毕竟我们自己也几乎总是在系统快要构筑完成的时候才能弄清楚它的设计,所以库作者的任务真地很艰巨. 麻烦的是库往往构造得不够好,而且往往不可能让我们修改其中的类使它完成我们希望完成的工作 。这是否意味那些经过实践检验的战术,如Move Method 等,如今都派不上用场了?

幸好我们有两个专门应付这种情况的工具.如果你只想修改库类的一两个函数, 可以运用Introduce Foreign  Method ;如果想要添加一大堆额外行为,就得运用Introduce Local Extension 。

2.20 Data Class (纯稚的数据类)

所谓Data Class是指它们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。这样的类只是一种不会说话的数据容器,它们几乎一定被其他类过份细琐地操控着.这些类早期可能拥有public字段,果真如此你应该在别人注意到它们之前,立刻运用 Encapsulate  Field将它们封装起来。如果这些类内含容器类的字段,你应该检查它们是不是得到了恰当的封装:如果没有,就运用 Encapsulate  Collection把它们封装起来。对于那些不该被其他类修改的字段,请运用Remove Setting Method 。

然后,找出这些取值/设值函数被其他类运用的地点。尝试 Move Method把那些调用行为搬移到 Data Class来。如果无法搬移整个函数,就运用 Extract Method 产生一个可被搬移的函数.不久之后你就可以运用 Hide Method把这些取值/设值函数隐藏起来了.

DataClass就像小孩子.作为一个起点很好,但若要让它们像成熟的对象那样参与整个系统的工作,它们就必须承担一定责任.

2.21 Refused Bequest (被拒绝的遗赠)

子类应该继承超类的函数和数据。但如果它们不想或不需要继承又该怎么办呢?它们得到所有礼物,却只从中挑选样来玩!

按传统说法,这就意味着继承体系设计错误.你需要为这个子类新建一个兄弟类 ,再运用Push Down Method (328)和Push Down Field (329)把所有用不到的函数下推给那个兄弟 。这样一来,超类就只持有所有子类共事的东西. 你常常会听到这样的建议:所有超类都应该是抽象 (abstract) 的。

既然使用"传统说法"这个略带贬义的词 ,你就可以猜到 ,我们不建议你这么傲,起码不建议你每次都这么傲。我们经常利用继承来复用一些行为,并发现这可 以很好地应用于日常工作.这也是一种坏味道,我们不否认.但气味通常并不强烈 。 所以我们说 :如果Refused Bequest引起困惑和问题,请遵循传统忠告 。但不必认为 你每次都得那么做。十有八九这种坏味道很淡,不值得理睬.

如果子类复用了超类的行为(实现) .却又不愿意支持超类的接口 .Refused Bequest 的坏味道就会变得浓烈 .拒绝继承超类的实现,这 点我们不介意:但如果拒绝继承超类的接口.我们不以为然。不过即使你不愿意继承接口,也不要胡乱修改继承体系, 应该运用Replace Inheritance with Delegation 来达到目的。

2.22 Comments(过多的注释)

别担心 ,我们并不是说你不该写注释 .从嗅觉上说. Comments不是一种坏味道,事实上它们还是一种香味呢.我们之所以要在这里提到Comments 是因为人们常把它当作除臭剂来使用。常常会有这样的情况:你看到一段代码有着长长 的注释,然后发现,这些注释之所以存在乃是因为代码很糟糕.这种情况的发生次数之多,实在令人吃惊。

Comments可以带我们找到本章先前提到的各种坏味道 .找到坏味道后,我们首先应该以各种重构手法把坏味道去除。完成之后我们常常会发现:注释已经变得多余了,因为代码已经清楚说明了一切.

如果你需要注释来解释一块代码做了什么,试试 Extract  Method;如果函数已经提炼出来 ,但还是需要注释来解释其行为,试试 Rename Method;如果你需要注释说明某些系统的需求规格,试试Introduce Assertion。

如果你不知道该做什么,这才是注释的良好运用时机.除了用来记述将来的打算之外,注释还可以用来标记你并无十足把握的区域。你可以在注释里写下自己"为什么做某某事”这类信息可以帮助将来的修改者,尤其是那些健忘的家伙.

代码的坏味道我们基本介绍完了,本次12个坏味道基本都和类有关系,所以我们在设计和后续修改的时候一定要注意当前操作的类的继承层次,用途等是否符合设计原则。

正文到此结束
Loading...