您的位置:首页 > 编程语言

重构_改善既有的代码设计(三)

2014-04-24 17:34 288 查看
重构,需要培养出自己的判断力,学会判断一个类内有多少实例变量算是太大,一个函数内有多少行代码才算太长。

1、重复代码

(1)最单纯的重复代码是“同一个类的两个函数含有相同的表达式”,只需要抽出相同的代码,两个地方都调用就可以了。如果是两个毫不相干的类出现重复代码,你应该考虑将代码抽到一个独立类当中。但是,重复代码所在的函数也可能的确只属于某个类,另外一个类只能调用它。你必须决定这个函数放在哪儿最合适,并确保它被安置后就不会再在其他任何地方出现。

(2)如果两个互为兄弟的子类内含相同表达式,需要提炼出代码推入超类内。如果代码只是相似,并非完全相同,那么就得分离相似部分和差异部分。(这里提到一个Template Method设计模式,补上)

2、过长函数

程序越长越难理解。“间接层”所能带来的全部利益--解释能力、共享能力、选择能力都是由小型函数支持的。现代OO语言几乎已经完全免除了进程内的函数调用开销。不过代码阅读者还是得多费力气,因为他必须经常转换上下文去看看子程序做了什么。如果你能给函数起个好名字,读者就可以通过名字了解函数的作用,根本就不必去看其中写了什么。条件表达式和循环常常是提炼的信号。你可以使用Decompose Conditional(这应该是一种处理方法,分解条件?)处理条件表达式。至于循环,你应该将循环和其内的代码提炼到一个独立函数中。

3、过大的类

如果想利用单个类做太多事情,其内往往就会出现太多实例变量。可以讲几个变量一起提炼至新类内。提炼时应该选择类内彼此相关的变量,将它们放在一起。例如depositAmount和depositCurrency可能应该隶属同一个类。通常如果类内的数个变量有着相同的前缀或字尾,这就意味着有机会把它们提炼到某个组件内。如果这个组件适合作为一个子类,你会发现Extract Subclass往往比较简单。如果类内有太多代码,最简单的解决方案是把东西消弭于类内部,比如有五个“百行函数”,它们之中很多代码都相同,那么或许你可以把它们变成五个“十行函数”和十个提炼出的“双行函数”。如果你的Large Class是一个GUI类,你可能需要把数据和行为移到一个独立的领域对象去。你可能需要两边各保留一些重复数据,并保持两边同步。

4、过长的参数列表

过长的参数列表难以理解。函数所需的所有东西都以参数传递进去,除此之外只能选择全局数据,而全局数据是邪恶的东西。对象技术改变了这一情况:如果你手上没有所需的东西,总可以叫另一个对象给你。此为对象参数。

5、Divergent Change(发散式变化)

我们希望软件能够更容易被修改。一旦需要修改,我们希望能够跳到系统的某一点,只在该处做修改。如果某个类经常因为不同的原因在不同方向上发生变化,Divergent Change就出现了。比如:新加入一个数据库,必须修改三个函数;新出现一种金融工具,必须修改四个函数。那么此时也许将这个对象分成两个会更好,这样一来每个对象就可以只因一种变化而需要修改。当然,往往在加入新数据库或新金融工具后,你才能发现这一点。

6、Shotgun Surgery(霰弹式修改)

如果每遇到某种变化,你都必须在许多不同的类中做出许多小修改,你所面临的坏味道就是Shotgun Surgery。如果需要修改的代码三步四处,你不但很难找到它们,也很容易忘记某个重要的修改。这种情况下你应该把所有需要修改的代码放进同一个类,如果眼瞎没有合适的类可以安置这些代码,就创造一个。

7、Feature Envy(依恋情结)

对象技术的全部要点在于:这是一种“将数据和对数据的操作行为包装在一起”的技术。函数可能因为数据,对某个类的兴趣高过对自己所处类的兴趣。这时候需要把它移到该去的地方。如果复杂一点,一个函数往往会用到几个类的功能,那么我们的原则是:判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据摆在一起。这里再次提到Strategy模式(复习),还有Visitor模式(补上)

8、Data Clumps(数据泥团)

你常常可以在很多地方看到相同的三四项数据:两个类中相同的字段、许多函数签名中相同的参数。这些总是绑在一起出现的数据真应该拥有属于它们自己的对象。可能在使用时,只用上了新对象的部分字段,但是只要以新对象取代两个(或更多)字段,你就值回票价了。得到新对象之后,你就可以着手寻找Feature Envy,这可以帮你指出能够移至新类中的种种程序行为。不必太久,所有类都将在它们的小小社会中充分发挥价值。

9、Primitive Obsession(基本类型偏执)

大多数变成环境都有两种数据:结构类型允许你将数据组织成有意义的形式;基本类型则是构成结构类型的积木块。对象的一个极大的价值在于:它们模糊(甚至打破)了横亘于基本数据和体积较大的类之间的界限。你可以尝试在小任务上用小对象--像是结合数值和币种money类、有一个起始值和一个结束值组成的range类。将单独存在的数据值替换为对象,从而走出传统的窟窿,进入炙手可热的对象世界。

10、Switch Statements(switch惊悚现身)

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

11、Parallel Inheritance Hierarchies(平行继承体系)

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

12、Lazy Class(冗赘类)

你所创建的每一个类,都得有人去理解它、维护它,这些工作都是要花钱的。如果一个类的所得不值其身价,它就应该消失。项目中经常会出现这样的情况,某个类原本对得起自己的身价,但重构使它身形缩水,不再做那么多工作;或开发者事先规划了某些变化,并添加一个类来应付这些变化,但变化实际上没有发生。

13、Speculative Generality(夸夸其谈未来性)

有些代码如果暂时用不到,就把它去掉。而不是说“我总有一天会用到”,这么做的结果往往造成系统更难理解和维护。

14、Temporary Field(令人迷惑的暂时字段)

有时你会看到这样的对象:其内某个实例变量(就是非static修饰的变量,也被称为成员变量。对应的是类变量,static修饰的变量)仅为某种特定情况而设。这样的代码让人不易理解,因为你通常认为对象在所有时候都需要它的所有变量。在变量未被使用的情况下猜测当初其设置目的,会让你发疯的。把所有和这个变量相关的代码都放进一个新类。也许你还可以在变量不合法的情况下,创建一个null对象(空对象,应该是这个意思),从而避免写出条件式代码

15、Message Chains(过渡耦合的消息链)

如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象。。。。。。这就是消息链。实际代码中你看到的可能是一长串的getThis()或一长串临时变量。采用这种方式,意味客户代码将与查找过程中的导航结构紧密耦合。一旦对象间的关系发生任何变化,客户端就不得不做出相应修改。你可以再消息链的不同位置进行这种重构手法。理论上可以重构消息链上的任何一个对象,但这么做往往会把一系列对象都变成Middle Man。通常更好的选择是:先观察消息链最终得到的对象是用来干什么的,看看能否把使用该对象的代码提炼到一个独立函数中,然后把这个函数推入消息链。如果这条链上的某个对象有多位客户打算航行此航线的剩余部分,就加一个函数来做这件事。

16、Middle Man(中间人)

对象的基本特征之一就是封装--对外部世界隐藏其内部细节。封装往往伴随委托。比如说你问主管是否有时间参加一个会议,他就把这个消息“委托”给他的记事簿,然后才能回答你。你没有必要知道这位主管到底使用传统记事簿或电子记事簿亦或秘书来记录自己的约会。但是人们可能过渡运用委托。你也许会看到某个类接口有一半的函数都委托给其他类,这样就是过渡运用。这时应该去掉中间人,直接和真正负责的对象打交道。如果这样“不干实事”的函数只有少数几个,可以把它们放进调用端。如果这些Middle Man还有其他行为,可以把它变成实则对象的子类,这样你既可以扩展原对象的行为,又不必负担那么多的委托动作。

17、Inappropriate Intimacy(狎昵关系)

有时你会看到两个类过于亲密,花费太多时间去探究彼此的private成分,你就需要帮它们划清界限,减少狎昵行径。如果它们有一些共同点,可以将共同点提炼到一个新类中。继承往往造成过度亲密,因为子类对超类的了解总是超过后者的主观愿望。如果你觉得该让这个孩子独自生活了,可以让它离开继承体系。

18、Alternative Classes with Different Interfaces(异曲同工的类)

如果两个函数做同一件事,却有着不同的签名(应该是函数名的意思),根据它们的用途重新命名。但这往往不够,请反复移动方法将某些行为移入类,直到两者的协议一致为止。(协议?)。如果你必须重复而赘余地移入代码才能完成这些,或许可以尝试抽出父类。

19、Incomplete Library Class(不完美的库类)

所谓Data Class是指:它们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。这样的类只是一种不会说话的数据容器,它们几乎一定被其他类过分细琐地操控着。这些类可能拥有public字段,注意封装。如果这些类内含容器类的字段,你应该检查他们是不是得到了恰当的封装。找出这些取值/设置函数被其他类运用的地点,尝试将那些调用行为搬移到Data Class来。如果无法搬移整个函数,就抽出一个可被搬移的函数。不久之后,你就可以把这些取值/设置函数隐藏起来了。Data Class就像小孩子。作为一个起点很好,但若要让它们像成熟的对象那样参与整个系统的工作,它们就必须承担一定责任。

20、Comments(过多的解释)

常常会有这样的情况:你看到一段代码有着常常的注释,然后发现,这些注释之所以存在乃是因为代码很糟糕。当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余。如果你不知道该做什么,这才是注释的良好运用时机。除了用来记述将来的打算之外,注释还可以用来标记你并无十足把握的区域。你可以在注释里写下自己“为什么做某某事”。这类信息可以帮助将来的修改者,尤其是那些健忘的家伙。

这一章主要讲述代码的坏味道,哪些地方可能需要重构,增加自己重构的判断力。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: