改善既有代码的设计(三)----------代码的坏味道

我喜欢“坏味道”这个词,代码狗嗅到了项目中的坏味道,就需要来重构,去除这种坏味道,好了不多说废话了,看看作者给我们提了哪些项目中的坏味道。come  on 

3.1 Duplicated Code (重复代码)

 这是我们开发中最常见的,如果你在一个以上的地方看到相似的程序结构,那么就可以肯定:设法将他们合二为一,程序会变得更好。

  最简单的就是“同一个类的两个函数含有相同的表达式”,采用 Extract Method 提炼出重复代码,然后让着两个地方都调用被提炼出来的函数即可。

  另一种常见的就是“两个互为兄弟的子类含有相同的表达式”,这时我们一般Extract Method  然后对被提炼出的代码使用Pull Up Method,推入父类即可。

  如果两个毫不相关的类出现Duplicated Code ,我们就应该考虑对其中一个使用Extract Class,将重复代码提炼到一个独立类中,然后在另一个类中使用这个新类,但是,重复代码所在的函数也可能的确只属于某个类,另一个类只能调用它,抑或这个函数可能属于第三个类,而另外两个类应该引用这第三个类,你必须决定这个函数放在那里最合适,并确保它被安置以后就不会在其他任何地方出现

3.2 Long Method(过长函数)

 拥有段函数的对象会活的比较好,比较长,(表示不理解),“间接层”所能带来的全部利益----解释能力,共享能力,选择能力,这些都是由小型函数支持的,让小函数容易理解的真正关键之处在于给他一个好名字,读者可以通过函数名字来了解函数的作用,根本不必去看其中写了什么。

  所以我们应该更积极的去分解函数,而应该遵循的原则:当你感觉需要用注释来说明点什么的时候,我们就需要把说明的东西写进一个独立的函数中,并以其用途(而非实现手法)命名。

  我们可以对一组甚至一行代码做这件事情,哪怕替换后的函数调用动作比函数自身还要长,只要函数名字能够替代其用途,我们也该毫不犹豫的这么做,(比如之前看oc的方法感觉上就是名字特别长)

  如果函数内有大量的参数和临时变量,会对提炼函数造成阻碍,如果我们把许多参数和临时变量当做参数,传递给被提炼处理啊的新函数,导致可读性几乎没有任何提升,此时我们 可以运用Replace Temp with Query 来消除这些临时元素,Introduce Parameter Object 和Preserve Whole Object 则可以将这些过长的参数列表变得更简洁一些。 

  如何确定该提炼哪一段代码呢?一个技巧就是:寻找注释。他们通常能指出代码用途和实现手法之间的语义距离,签过代码前方有一行注释,就是在提醒你:可以将这一段嗲吗替换成一个函数,而且可以再注释的基础上给这个函数命名。就算只有一行代码需要注释也值得将他提炼到独立函数中。

  条件表达式和循环也常常是提炼的信号,可以用Decompose Conditional 处理条件表达式,至于循环,你应该将循环和其中的代码提炼到一个独立函数中。

3.3 Large Class (过大的类)
  如果单个类做太多事情,内部往往出现太多实例变量,一旦如此,Duplicater Code 也就接踵而至了。
  可以运用Extract Class 将几个变量提炼到一个新类中,我们在Extrac Class 和Extract Subclass 时有个技巧:先确定客户端如何使用他们,然后运用Extract Interface 为每一种使用方式提炼出一个接口,这或许可以帮助你看清楚如何分解这个类。
3.4 Long Parameter List (过长参数列表)
  太长的参数列难以理解,太多参数会造成前后不一致,不易使用,而且一旦函数需要更多数据,就不得不修改它,而如果一旦将对象传给函数,大多数修改都将没有必要,因为你很可能只需要在函数内增加一两条请求,就能达到更多数据。我们可以用Preserve Whole Object 将来自同一对象的一堆数据收集起来,并以该对象替换他们,如果这些数据缺乏合理的对象归属,可以使用Introduce Parameter Object 为她们制造出一个参数对象
  这里有一个重要的例外:有时候你明显不希望造成”被调用对象“与”较大对象“间的依赖关系,这时候将数据从对象中拆解出来单独作为参数,也很合情合理,但是请注意其引发的代价。如果参数列过长或者变化太频繁,你就需要重新考虑自己的依赖结构了。  
3.5 Divergent Change(发散式变化)
如果某个类经常因为不同的原因在不同的方向上发生变化,Divergent Change就出现了,当你看着一个类,说:如果新加入一个数据库,我必须修改这三个函数,如果出现一种新工具,我必须修改这四个函数,那么此时,也许将这个对象分成两个比较好,这么一来,每个对象就可以只因一种变化而需要修改。针对某一外界变化的所有相应修改,都只应该发生在单一类中,而这个新类内的所有内容都应该反应此变化,为此,你应该找出某特定原因而造成的所有变化,然后运用Extract Class 将他们提炼到另一个类中。
3.6 Shotgun Surgery (散弹式修改)
Shotgun Surgery 指的是”一种变化引发多个类相应的修改“Divergent Change指的是”一个类受多种变化的影响“,这种情况下,应该使用Move Method 和Move Field 把所有需要修改的代码放进同一个类,如果眼下没有合适的类可以安置这些代码,就创造一个,通常可以运用Inline Class把一系列相关行为放进同一个类,这可能会造成少量的Divergent Change,但是你可以轻易处理它。
3.7 Feature Envy(依恋情结)
  函数对某个类的兴趣高于了对自己所处类的兴趣,这种依恋情节最通常的焦点就是数据。无数次经验里边可以看到某个函数为了计算某个值几乎调用了另外一个对象半打儿的取值函数,疗法很显然:把这个函数移到另外一个地方。
  当然并非所有情况都这么简单,一个函数往往用到几个类的功能,那么它应该被放在哪里呢?原则就是:判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据摆放在一起,如果先Extract Method将函数分解成数个较小的函数并分别置于不同点,上述步骤就很容易完成了。
3.8 Data Clumps (数据泥团)

  在很多地方我们可以看到相同的三四项数据,两个类中相同的字段,许多函数签名中相同的参数,这些总是绑在一起出现的数据真应该拥有属于他们自己的对象,首先请找出这些数据已字段形式出现的地方,运用Extract Class 将他们提炼到一个独立对象中,然后将注意力转移到函数签名上,运用Introduce Parameter Object 或者Preserve Whole Object 为他减肥,这么做的直接好处就是可以将很多参数列缩短,简化函数调用。是的,不必在意Data Clumps只用上新对象的一部分字段,只要以新对象取代两个或以上的字段,那你就值回票价了。

一个好的评判办法:删掉众多数据项中的一项,其他数据没有因此失去意义。如果他们不再有意义,这就是一个信号:你应该为她们产生一个新对象,拥有新对象以后,你就可以着手寻找Feature Envy,这可以帮助你指出能够移至新类中的种种程序行为,不必太久,所有的类都将在他们的小小社会中充分发挥价值

3.9 Primitive Obsession (基本类型偏执)

  对象的一个极大价值在于:他们模糊(甚至打破)了横亘于基本数据和体积较大的类之间的界限,你可以轻松写出一些与基本类型无异的小型类。

  对象技术的新手一般不愿意在小任务上运用小对象,像是结合数值和币种的money类等特殊字符串,你可以运用Replace Data Value with Object 将原本单独存在的数据替换为对象,从而走出传统的洞窟,进入炙手可热的对象世界

3.10 Switch Statements(switch 惊悚现身)

面向对象程序的一个最明显特征就是:少用switch(或者case语句)从本质来说,switch语句的问题在于重复,你会发现同样的switch语句散布于不同地点,如果你想为他添加一个新的case语句,就必须找到多有的switch语句并修改他们,面相对象中的多态可以为此带来优雅的解决办法。

那么问题就出来了,多态应该出现在哪里?switch语句常根据类型码进行选择,你要的是与该类型码相关的类或者函数,所以应该使用Extract Method 将switch语句提炼到一个独立函数中,再以Move Method 将它搬移到需要多态的那个类中,此时你必须决定是否使用Replace Type Code with Subclass或者Replace Type Code with State/Strategy,一旦完成继承结构以后,你就可以运用Replace Conditional with Polymorphism

如果你只是在单一函数中有些选额事例,且不想改动他们,那么多态就有点杀鸡用牛刀了,这种情况下,Replace Parameter with Explicit Method 是个不错的选择,如果你的选择条件之一是null,可以试试Introduce Null Object

3.12 Lazy Class(冗赘类)

  我们创建的每一个类都需要人来维护它,理解它,这都是需要花钱的,如果一个类所得不值其身价,就应该消灭它了,尤其是在重构过程让他身形缩水,那么我们就Collapse Hierarchy 对于几乎没用的组件,就应该Inline Class对付他们

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

这个坏味道在项目中的体现就是,企图用各种各样的钩子和特殊情况来处理一些非必要的情况,这样做的结果就是造成系统更难理解和维护,如果某个抽象咧其实没有太大作用,请运用Collapse Hierarchy (折叠继承体系) 不必要的委托可以运用Inline Class 除掉,如果函数的某些参数未被用上,就可以对他实施Remove Parameter 如果函数名称带有多余的抽象意味,就Rename Method.让他现实一些

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

  有时候某个对象中 某个实例变量仅为某种特定情况而设定,这样的代码容易让人不理解,因为我们通常认为对象在所有时候都需要他的所有变量,在变量未被使用的情况下猜测当初设置目的,会让人疯掉的

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

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

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

  如果你看到用户向一个对象请求另一个对象,再向后者请求另外一个对象。。。。。。这就是消息链,这意味着客户端代码将与查找过程中的导航结构紧密耦合,一旦对象间发生任何变化,客户端就不得不做出相应修改。

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

3.16 Middle Man (中间人)

对象的基本特征之一就是封装-------对外部世界隐藏其内部细节,封装往往伴随委托,但是人们可能过度运用委托,你也许看到某个类接口,有一半的函数都委托给其他类,这样就是过度运用,这时应该使用Remove Middle Man 直接和真正负责的对象打交道,如果这样不干实事的函数只有少数几个,可以运用Inline Method 把他们放进调用端,如果这些Middle Man 还有其他行为,可以运用Replace Delegate with Inheritance 把他们变成实责对象的子类,这样就既可以扩展原对象的行为,又不必负担那么多委托责任。

3.17 Inappropriate Intimacy (狎昵关系)

  两个类太过于亲密,就需要花费太多时间去探究彼此的private 成分,就像古代恋人亦一样,过于狎昵的类必须拆散,你可以采用Move Method 和Move Field 帮他们划定界限,从而减少狎昵行径,你也可以看看是否可以运用Change Bindirectional Association to Unidirectional(将双向关联改为单向关联)让其中一个类对另一个斩断情丝,如果两个类实在是情投意合,可以运用Extract Class 把两者共同点提炼到一个安全地点,让他们坦荡的使用这个新类,或者也可以尝试运用Hide Delegate 让另外一个类来为她们传递相思

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

  如果两个函数做同一件事情,却有不同的签名,请运用Rename Mehtod 根据他们的用途重新命名,但这往往不够,请反复运用Move Method 将某些行为移入类,知道两者的协议一致为止。

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

  复用经常被认为是程序的终极目的,不过我们认为,复用的意义经常被高估:大多数对象只要够用就好,但是不可否认,很多编程技术都是建立在程序库的基础上,我么都没有未卜先知的能力,所以往往在程序快要结束的时候才能真正清楚他的设计,所以库作者的任务真的很艰巨,在面临当前程序库不能满足我们的需要时,一般可以这么做:如果 你只想修改库类的一两个函数,可以运用Introduce Foreign Method (引入外加函数),如果想添加一大堆额外行为,就得运用Introduce Local Extension(引入本地扩展)

3.20 Data Class(纯稚的数据类)

  这里对java来说,就是我们常用的javabean,或者叫model,entry等,基本上除了拥有一些字段和用于访问读写这些字段的函数以外,其他事一无长物,就是一个不会说话的数据容器,他们几乎一定被其他类过分细琐的操控着,这些类早期可能拥有public字段,如果是这样那我们应该行动起来了,立刻运用Encapsulate Field (封装字段)将他们封装起来,如果这些类含容器类的字段,就应该检查他们是不是得到了好的封装,如果没有,就运用Encapsulate Collection 将他们封装起来,对于那些不该被其他类修改的字段,请运用Remove Setting Method(移除设值函数)。

  然后找出这些取值设置函数被其他类运用的地方,尝试以Move Method 把那些调用行为搬移到Data Class 来,如果无法搬移整个函数,就运用Extract Method 产生一个可被搬移的函数,不久以后就可以运用Hide Method 把这些取值设值函数隐藏起来了。Data Class 就像小孩子,作为一个起点很好,但是若要让他们想成熟的对象那样参与整个系统的运作,他们就必须承担一定责任。

3.21 Refused Bequest(被拒绝的遗赠)

 



 


   

  










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值