为什么老是有人觉得别人的代码臭?来看看代码坏味道再决定到底谁的代码更香

你是否被别人嫌弃过代码?是否看不起别人的代码却不敢说?是否觉得自己人微言轻不敢言?跟我一起看看什么是臭代码吧,挺起胸膛做码农!

最近看了Martin Flowler所著《重构 改善既有代码的设计》其中代码的坏味道一章告诉我们在看到什么样的代码时就应该思考重构了,下面我就把22种坏味道以我的理解整理了一下。

重复代码(Duplicated Code)

1.一个类中含有相同的表达式,这个时候就要使用Extract Method将相同的表达式提炼出来

2.「两个互为兄弟〔sibling)的subclasses内含相同表达式」。要避免这种情况,只需对两个classes都使用Extract Method,然后再对被提炼出来的代码使用 Pull Up Field,将它推入superclass内。

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

过长函数(Long Method)

拥有[短函数」(short methods)的对象会活得比较好、比较长
你应该更积极进取地分解函数。我们遵循这样一条原则:每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。我们可以对一组或甚至短短一行代码做这件事。哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,我们也该毫不犹豫地那么做。关键不在于函数的长度,而在于函数「做什么」和「如何做」之间的语义距离
百分之九十九的场合里,要把函数变小,只需
1.使用Extract Method。找到函数中适合集在一起的部分,将它们提炼出来形成一个新函数。
2.如果函数内有大量的参数和临时变量,它们会对你的函数提炼形成阻碍。如果你尝试运用Extract Method,最终就会把许多这些参数和临时变量当作参数,传递给被提炼出来的新函数,导致可读性几乎没有任何提升。
3.你可以经常运用Replace Temp with Query 来消除这些暂时元素。Introduce Parameter Object 和Preserve Whole Object 则可以将过长的参数列变得更简洁一些。
4.如果你已经这么做了,仍然有太多临时变量和参数,那就应该使出我们的杀手锏: Replace Method with Method Object。

过大的类(Large Class)

如果想利用单一class做太多事情,其内往往就会出现太多instance变量。一旦如此,Duplicated Code也就接踵而至了。
你可以运用Extract Class将数个变量一起提炼至新class内。提炼时应该选择class内彼此相关的变量,将它们放在一起
如果一个类过大,也要审视一下这个类是否符合单一职责原则

过长参数列(Long Parameter List)

太长的参数列难以理解,太多参数会造成前后不一致、不易使用,而且一旦你需要更多数据,就不得不修改它。如果将对象传递给函数,大多数修改都将没有必要,因为你很可能只需(在函数内)增加一两条请求(requests),就能得到更多数据。
如果「向既有对象发出一条请求」就可以取得原本位于参数列上的一份数据,那么 你应该激活重构准则Replace Parameter with Method 。上述的既有对象可能是函数所属class内的一个值域(field),也可能是另一个参数。你还可以运用Preserve Whole Object 将来自同一对象的一堆数据收集起来,并以该对象替换它们。如果某些数据缺乏合理的对象归属,可使用Introduce Parameter Object 为它们制造出一个「参数对象」。

Divergent Change(发散式变化)

如果某个class经常因为不同的原因在不同的方向上发生变化,Divergent Change就出现了。当你看着一个class说:『呃,如果新加入一个数据库,我必须修改这三个函数;如果新出现一种金融工具,我必须修改这四个函数』,那么此时也许将这个对象分成两个会更好,这么一来每个对象就可以只因一种变化而需要修改。当然,往往只有在加入新数据库或新金融工具后,你才能发现这一点。针对某一外界 变化的所有相应修改,都只应该发生在单一class中,而这个新class内的所有内容都应该反应该外界变化。为此,你应该找出因着某特定原因而造成的所有变化,然后运用Extract Class 将它们提炼到另一个class中。

Shotgun Surgery(散弹式修改)

Shotgun Surgery类似Divergent Change,但恰恰相反。如果每遇到某种变化,你都必须在许多不同的classes内做出许多小修改以响应之,你所面临的坏味道就是Shotgun Surgery。**如果需要修改的代码散布四处,你不但很难找到它们,也很容易忘记某个重要的修改。
这种情况下你应该使用Move Method 和 Move Field 把所有需要修改的代码放进同一个class。**如果眼下没有合适的可以安置这些代码,就创造一 个。通常你可以运用Inline Class 把一系列相关行为放进同一个class。这可能会造成少量Divergent Change,但你可以轻易处理它。
Divergent Change是指「一个class受多种变化的影响」,Shotgun Surgery则是指「一种变化引发多个classes相应修改」。这两种情况下你都会希望整理代码,取得「外界变化」与「待改类」呈现一对一关系的理想境地。

Feature Envy(依恋情结)

数对某个class的兴趣高过对自己所处之host class的兴趣。这种孺慕之情最通常的焦点便是数据。无数次经验里,我们看到某个函数 为了计算某值,从另一个对象那儿调用几乎半打的取值函数(getting method)。疗法显而易见:把这个函数移至另一个地点。你应该使用Move Method 把它 移到它该去的地方
有时候函数中只有一部分受这种依恋之苦,这时候你应该使用 Extract Method 把这一部分提炼到独立函数中,再使用Move Method 带它去它的梦中家园。
当然,并非所有情况都这么简单。一个函数往往会用上数个特性,那么它究竟该被置于何处呢?我们的原则是:判断哪个class拥有最多「被此函数使用」的数据,然后就把这个函数和那些数据摆在一起。如果先以Extract Method 将这个函数分解为数个较小函数并分别置放于不同地点,上述步骤也就比较容易完成了。

Data Clumps(数据泥团)

两个classes内的相同值域(field)、许多函数签名式(signature)中的相同参数。这些「总是绑在一起出现的数据」真应该放进属于它们自己的对象中
首先请找出这些数据的值域形式(field)出现点,运用Extract Class 将它们提炼到一个独立对象中。然后将注意力转移到函数签名式(signature)上头,运用Introduce Parameter Object 或Preserve Whole Object 为它减肥。这么做的直接好处是可以将很多参数列缩短,简化函数调用动作。是的,不必因为Data Clumps只用上新对象的一部分值域而在意,只要你以新对象取代两个(或更多)值域,你就值回票价了。
一个好的评断办法是:删掉众多数据中的一笔。其他数据有没有因而失去意义?如果它们不再有意义,这就是个明确信号:你应该为它们产生一个新对象。

Primitive Obsession(基本型别偏执)

可以运用Replace Data Value with Object将原本单独存在的数据值替换为对象。如果欲替换之数据值是 type code(型别码),而它并不影响行为,你可以运用 Replace Type Code with Class 将它换掉。如果你有相依于此 type code的条件式,可运用 Replace Type Code with Subclasses 或 Replace Type Code with State/Strategy 加以处理。
如果你有一组应该总是被放在一起的值域(fields),可运用Extract Class。 如果你在参数列中看到基本型数据,不妨试试Introduce Parameter Object。 如果你发现自己正从array中挑选数据,可运用Replace Array with Object。

Switch Statements(switch惊悚现身)

大多数时候,一看到switch语句你就应该考虑以「多态」来替换它。问题是多态 该出现在哪儿?switch语句常常根据 type code(型别码)进行选择,你要的是「与 该 type code相关的函数或class」。所以你应该使用Extract Method 将switch语句提炼到一个独立函数中,再以Move Method 将它搬移到需要多态性的那个class里头。此时你必须决定是否使用 Replace Type Code with Subclasses 或 Replace Type Code with State/Strategy。一旦这样完成继承结构之后, 你就可以运用Replace Conditional with Polymorphism了。(通常的做法是采用策略模式,同样的太多的if else 也应该这么处理)
如果你只是在单一函数中有些选择事例,而你并不想改动它们,那么「多态」就有 点杀鸡用牛刀了。这种情况下Replace Parameter with Explicit Methods是个不错的选择。如果你的选择条件之一是null,可以试试Introduce Null Object。

平行继承体系(Parallel Inheritance Hierarchies)

平行继承体系是霰弹式修改的的特殊情况,在这种情况下每当为一个类增加子类时必须为另外一个类也增加子类。解决方法时让一个继承体系的实例引用另一个继承体系的实例,再使用Move Method和Move Field

Lazy Class(冗赘类)

你所创建的每一个class,都得有人去理解它、维护它,这些工作都是要花钱的。如 果一个class的所得不值其身价,它就应该消失。重构后已经不必存在的类就让它彻底消失

Speculative Generality(夸夸其谈未来性)

当有人说『噢,我想我们总有一天需要做这事』并因而企图以各式各样的挂勾(hooks)和特殊情况来处理一 些非必要的事情,这种坏味道就出现了。那么做的结果往往造成系统更难理解和维护。如果所有装置都会被用到,那就值得那么做;如果用不到,就不值得。用不上的装置只会挡你的路,所以,把它搬开吧。
如果你的某个abstract class其实没有太大作用,请运用Collapse Hierarchy。非必要之delegation (委托)可运用Inline Class 除掉。如果函数的某些参数未被用上,可对它实施 Remove Parameter。如果函数名称带有多余的抽象意味,应该对它实施Rename Method 让它现实一些。
如果函数或class的惟一用户是test cases (测试用例),这就飘出了坏味道Speculative Generality。如果你发现这样的函数或class,请把它们连同其test cases都删掉。但如果它们的用途是帮助test cases检测正当功能,当然必须刀下留人。

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

类的某个实例变量仅为某种特定情况而设,使用Extract Class(149)给这个变量创建一个新对象,把和变量相关的行为搬过来。

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

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

Middle Man(中间转手人)

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

狎昵关系(Inappropriate Intimacy)

有时你会看到两个classes过于亲密,花费太多时间去探究彼此的private成分。过份狎昵的classes必须拆散。

你可以采用 Move Method 和 Move Field 帮它们划清界线,从而减少狎昵行径。你也可以看看是否运用 Change Bidirectional Association to Unidirectional 让其中一个class对另一个斩断情丝。如果两个实在是情投意合,可以运用Extract Class 把两者共同点提炼到一个安全地点,让它们坦荡地使用这个新class。或者也可以尝试运用 Hide Delegate 让另一个class来为它们传递相思情。

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

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

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

不完美的类库(Incomplete Library Class)

如果只想修改库类的一两个函数可以运用Introduce Foreign Method,如果要添加一大堆行为就得运用Introduce Local Extension。

Data Class(纯稚的数据类)

所谓Data Class是指:它们拥有一些值域(fields),以及用于访问(读写〕这些值域的函数,除此之外一无长物。这样的classes只是一种「不会说话的数据容器」

Refused Bequest(被拒绝的遗贈)

Subclasses 应该继承superclasses的函数和数据。但如果它们不想或不需要继承,又该怎么办呢?
按传统说法,这就意味继承体系设计错误。你需要为这个subclass 新建一个兄弟(sibling class),再运用Push Down Method 和 Push Down Field 把所有用不到的函数下推给那兄弟。这样一来superclass就只持有所有subclasses共享的东西。常常你会听到这样的建议:所有superclasses都应该是抽象的(abstract)。

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

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

Comments(过多的注释)

别担心,我们并不是说你不该写注释。常常会有这样的情况:你看到一段代码有着长长的注释,然后发现,这些注释之所以存在乃是因为代码很糟糕。这种情况的发生次数之多,实 在令人吃惊
Comments可以带我们找到本章先前提到的各种坏味道。找到坏味道后,我们首先应该以各种重构手法把坏味道去除。完成之后我们常常会发现:注释已经变得多余了,因为代码已经清楚说明了一切。

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

TIP:当你感觉需要撰写注释,请先尝试重构,试着让所有注释都变得多余。
如果你不知道该做什么,这才是注释的良好运用时机。除了用来记述将来的打算之外,注释还可以用来标记你并无十足把握的区域。你可以在注释里写下自己「为什 么做某某事」。这类信息可以帮助将来的修改者,尤其是那些健忘的家伙。

总结

坏代码出现在工作中的方方面面,经常让一些新手无所适从不敢下手。工作中没有一个代码好坏的评定标准,以至于学习别人的“臭代码”而不自知,甚至在之后的工作中奉为圣经,如果你想在以后的代码香不香的吵架中理直气壮,请关注后续重构手法文章,挺起胸膛敲代码,理直气壮怼leader!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值