16
2013
04

《重构-改善既有代码的设计》笔记 - 代码的坏味道

代码重构最重要的部分就是如何发现需要重构的代码,而本章节代码的坏味道正是为我们讲述如何需找这些坏味道。作者很形象的说明了这些需重构的代码。


重复代码  (Duplicated Code)

一个以上的地方看到相同的程序架构

  1. 同一类的两个函数含有相同的表达式

  2. 两个互为兄弟的子类内含相同表达式

  3. 两个不相干的类中出现重复代码

解决方案:Extract Method


过长函数 (Long Method)

每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并以它的用途命名。

如果代码前有一条注释,就是在提醒你:可以将这段代码换成一个函数,而且可以在注释的基础上给这个函数命名。

条件表达式和循环也是一个很好的提炼信号。


过大的类 (Large Class)

类内如果太多代码,也是代码重复、混乱并最终走向死亡的源头。

对于实例变量,如果类内的数个变量有着相同的前缀或后缀,这就意味着有机会把他们提炼到某个组件中。

解决方案:Extract Class, Extract Subclass, Extract Interface.


过长参数列 (Long Parameter List)

太长的参数列难以理解,太多参数会造成前后不一致、不易使用,而且一旦你需要更多数据,就不得不修改它。

对象技术:如果你手上没有所需的东西,总可以叫另一个对象给你。

例外:有时候你明显不希望造成“被调用对象”与“较大对象”间的某种依赖关系

忠告:如果参数列太长或变化太频繁,你就需要重新考虑自己的依赖结构了。


发散式变化 (Divergent Change)

如果某个类经常因为不同原因在不同方向上发生变化,发散式变化 (Divergent Change)就出现了。

针对某一外界变化的所有相应修改,都只应该发生在单一类中,而这个新类内的所有内容都应该反映此变化。


霰弹式修改 (Shotgun Surgery)

如果每遇到某种变化,你都必须在许多不同的类内做出许多小改动,你所面临的坏味道就是Shotgun Surgery。

与发散式变化的区别:Divergent Change是指“一个类受多种变化的影响”,Shotgun Surgery则是指“一种变化引发多个类的修改”。


依恋情结 (Feature Envy)

函数对某个类的兴趣高过对自己所处类的兴趣。

原则:判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据摆在一起。


数据泥团 (Data Clumps)

我们常常可以在很多地方看到相同的三四项数据:两个类中相同的字段、许多函数签名中相同的参数。

评判方法:删除众多数据中的一项。这么做,其他数据有没有因而失去意义?如果它们不再有意义,这就是你应该为他们产生一个新对象的明确信号。


基本类型偏执 (Primitive Obsession)

这节应该是让我们多用合适的自定义结构类型,而不要偏执于只用编程语言的基本类型。


switch惊悚现身 (Switch Statements)

面向对象程序的一个最明显特征就是:少用switch(或case)语句。

解决方案:多态

大多数情况下,一看到switch语句,你就该考虑以多态来替代它。如果你只是在单一函数中有些选择事例,且不想改动它们,那么多态就有点杀鸡用牛刀了。这时可以使用Replace Parameter with Explicit Methods(以明确函数取代参数)。


平行继承体系 (Parallel Inheritance Hierarchies)

Parallel Inheritance Hierarchies是ShotgunSurgery的特殊情况

每当你为某个类增加一个子类,必须也为另外一个类相应增加一个子类。

发现某个继承体系的类名称前缀和另外一个继承体系的类名称前缀完全相同

策略:让一个继承体系的实例引用另外一个继承体系的实例


冗赘类  (Lazy Class)

类的存在需要成本,如果一个类的所得不值其身价,它就应该消失。


夸夸其谈未来性  (Speculative Generality)

“我想我们总有一天需要做这事”,并因而企图以各种各样的钩子和特殊情况来处理一些非必要的事情时,Speculative Generality就出现了。

如果所有装置都会被用到,那就值得那么做;反之,就不值得。


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

对象内某个实例变量仅为某种特定情况而设。

如类中有一个复杂算法,需要好几个变量。把变量跟相应方法一起提取出到一个单独的类。


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

什么叫消息链?用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象…

代码中一长串的getThis()或一长串的临时变量,这就意味着客户代码将与查找过程中的导航结构紧密耦合。

策略:先观察消息链最终得到的对象是用来干什么的,看看能否以Extract Method把使用该对象的代码提取到一个独立函数中,再运用Move Method把这个函数推入消息链。


中间人  (Middle Man)

对象的基本特征就是封装,封装往往会伴随委托。如果看到某个类接口有一半的函数都委托给其他类,这样就是过度运用。


狎昵关系  (Inappropriate Intimacy)

两个类过于亲密,花费太多时间去探究彼此的private成分。

如果两个类实在情投意合,可以运用Extract Class把两者共同点提炼到一个安全的地方,让他们坦荡的使用这个新类。

继承往往造成过度亲密,因为子类对超类的了解总是超过后者的主观愿望。


异曲同工的类  (Alternative Classes withDifferent Interfaces)

如果两个函数做同一件事,却有着不同的签名。


不完美的库类  (Incomplete Library Class)

如果你只想修改类库的一两个函数,可以运用Introduce Foreign Method;如果你想添加一大堆额外行为,就运用IntroduceLocal Extension。


纯稚的数据类  (Data Class)

Data Class:它们拥有一些字段,以及用于访问这些字段的函数,除此之外一无长物。

  1. 把它封装起来

  2. 移除不该被其他类修改字段的设值函数

  3. 找出这些取值/设值函数被其他类运用的地点,并尝试把这些行为搬到Data Class中来。如果无法搬移过来,就提取一个可以被搬移的函数

  4. 过段时间你就可以把这些取值/设置函数隐藏起来了


被拒绝的遗赠  (Refused Bequest)

子类应该继承超类的函数和数据。但如果子类不想或不需要继承超类所有的数据时,味道就出来了。


过多的注释  (Comments)

并不是说你不该写注释。而是当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余。

  • 如果你需要注释来解释一块代码做了什么,试试Extract Method

  • 如果函数已经提炼出来,还是需要注释来解释其行为,试试Rename Method;

  • 如果你需要注释说明某些系统的需求规格,请试试Introduce Assertion


« 上一篇下一篇 »

评论列表:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。