前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何解决代码腐败的味道

如何解决代码腐败的味道

作者头像
微观技术
发布2020-08-20 15:31:42
8040
发布2020-08-20 15:31:42
举报
文章被收录于专栏:微观技术微观技术

一. Duplicated Code(重复代码)

  1. 如果你在一个以上的地点看到相同的程序结构,设法将他们合而为一,程序会变得更好。
  2. 同一个类的两个函数含有相同的表达式,采用Extract Method(提炼函数)提炼出重复的代码。
  3. 两个互为兄弟的子类含有相同的表达式,首先对两个类都使用Extract Method(提炼函数),然后再对提炼出来的代码使用Pull Up Method(函数上移),将它推入超类。
  4. 如果代码之间只是类似,并非完全相同,运用Extract Method(提炼函数)将相似部分和差异部分分割开,然后可以运用Form Template Method(塑造模板函数)获取一个莫模板方法。
  5. 如果有些函数以不同算法做相同的事情,你可与选择其中较清晰的一个,使用Substitute Algorithm(替换算法)将其他函数的算法替换掉。
  6. 如果两个毫不相关的类出现重复代码,应考虑对其中一个使用Extract Class(提炼类),将重复代码提炼到一个独立的类中。
  7. 重复代码所在的函数应该只属于某一个类,另一个类调用它;或者应该属于第三个类,另两个类引用这第三个类。决定重复函数的最合适位置,确保只有一份。

二. Long Method(过长函数)

  1. 拥有短函数的对象会活的比较好、比较长。
  2. 绝大部分情况下,要把函数变小,只需要使用Extract Method(提炼函数)。
  3. 使用Extract Method(提炼函数)时,如果函数中有个别参数和临时变量,可以把他们当做参数,传递给被提炼出来的新函数。
  4. 如果被提炼函数内有大量的参数和临时变量,可以运用Replace Temp with Query(以查询取代临时变量)来消除这些临时元素。
  5. 使用Introduce Parameter Object(引入参数对象)可以将过长的参数列变得更简洁一些。
  6. 如果被提炼函数仍然有太多临时变量和参数,可以使用Replace Method with Method Object(以函数对象取代函数)。
  7. 注释通常能够指出应该被提炼的代码。就算只有一行代码,如果它需要以注释来说明,那也值得将它提炼到独立函数去。
  8. 你可以使用Decompose Conditional(分解条件表达式)处理条件表达式。
  9. 你应该将循环和其内的代码提炼到一个独立的函数中。

三. Large Class(过大的类)

  1. 如果想利用单个类做太多事情,其内往往就会出现太多实例变量。一旦如此,重复代码也就接踵而至了。
  2. 可以运用Extract Class(提炼类)将几个彼此相关的实例变量一起提炼至新类内。如果被提炼出的类适合作为一个子类,使用Extract Subclass(提炼子类)往往比较简单。
  3. 有时候类并非所有时刻都使用所有实例变量。你可以多次使用Extract Class(提炼类)或Extract Subclass(提炼子类)。
  4. 和“太多实例变量”一样,类内如果有太多代码,也是代码重复、混乱并最终走向死亡的源头。
  5. 如果有五个“百行函数”,他们之中有很多代码相同,那么你也许可以把他们变成五个“十行函数”和十个提炼出的“双行函数”。
  6. 和“太多实例变量”一样,类内如果有太多代码,往往也适合使用Extract Class(提炼类)或Extract Subclass(提炼子类)。
  7. 如果你的过大的类是一个GUI类,你可能需要把数据和行为移到一个独立的类中。

四. Long Parameter List(过长参数列)

  1. 太长的参数列难以理解,太多参数会造成前后不一致、不易使用。
  2. 刚开始学习编程时,老师教我们:把函数所需的所有东西都以参数传递进去。这可以理解,因为除此之外只能选择全局数据,而全局数据是邪恶的东西。
  3. 对象技术改变了这一情况:如果你手上没有所需的东西,总可以叫另一个对象给你。有了对象,函数需要的东西多半可以在函数的宿主类中找到。面向对象程序中的函数,其参数列通常比在传统程序中短的多。
  4. 如果向已有的对象发出一条请求就可以取代一个参数,那么你应该激活重构手法Replace Parameter with Method(以函数取代参数)。
  5. 你还可以运用Preserve Whole Object(保持对象完整)将来自同一对象的一堆数据收集起来,并以该对象替换他们。
  6. 如果某些数据缺乏合理的对象归属,可使用Introduce Parameter Object(引入参数对象)为他们制造出一个“参数对象”。
  7. 这里有一个例外:有时候你明显不希望造成“被调用对象”与“较大对象”间的某种依赖关系。这时候将数据从对象拆解出来单独作为参数,也很合情合理。但是请权衡其所引发的代价。

五. Divergent Change(发散式变化)

  1. 我们希望软件能够更容易被修改。一旦需要修改,我们希望能够跳到系统的某一点,只在该处做修改。
  2. 如果某个类经常因为不同的原因在不同的方向上发生变化, Divergent Change(发散式变化)就出现了。
  3. 针对某一外界变化的所有相应修改,都只应该发生在单一类中。为此,你应该找出某特定原因而造成的所有变化,运用Extract Class(提炼类)将他们提炼到另一个类中。

六. Shotgun Surgery(散弹式修改)

  1. Shotgun Surgery(散弹式修改)类似Divergent Change(发散式变化),但恰恰相反。
  2. 如果每遇到某种变化,你都必须在许多不同的类内做出许多小修改,你所面临的坏味道就是Shotgun Surgery(散弹式修改)。
  3. 如果需要修改的代码散布四处,你不但很难找到他们,也很容易忘记某个重要的修改。
  4. 你应该使用Move Method(搬移函数)和Move Field(搬移字段)把所有需要修改的代码放进同一个类。
  5. 如果眼下没有合适的类可以安置这些代码,就创造一个。通常可以运用Inline Class(将类内联化)把一系列相关行为放进同一个类。
  6. Divergent Change(发散式变化) 是指“一个类受多种变化的影响”。Shotgun Surgery(散弹式修改)则是指“一种变化引起多个类响应修改”。这两种情况你都希望整理代码,使“外界变化”与“需要修改的类”趋于一一对应。

七. Feature Envy(依恋情节)

  1. 对象技术即是一种“将数据和对数据的操作行为包装在一起”的技术。
  2. Feature Envy(依恋情节)指的是:函数对某个类的兴趣高过对自己所处类的兴趣。
  3. 我们常常看到某个函数为了计算某个值,从另一个对象那儿调用了几乎半打的取值函数。此时,你应该使用Move Method(搬移函数)把它移到它该去的地方。
  4. 先使用Extract Method(提炼函数),将这个函数分解为数个较小函数并分别置于不同地点,有助于Move Method(搬移函数)重构手法的实施。
  5. 如果一个函数用到几个类的功能,那么需要判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据摆在一起

八. Data Clumps(数据泥团)

  1. 数据项就像小孩子,喜欢成群结队地待在一块儿。这些总是绑在一起出现的数据真应该拥有属于它们自己的对象。
  2. 首先运用Extract Class(提炼类)将他们提炼到一个独立对象中,然后将注意力转移到函数签名上,运用Introduce Parameter Object(引入参数对象)或Preserve Whole Object(保持对象完整性)为它减肥。这样做可以缩短参数列,简化函数调用。
  3. 如果删掉众多数据中的一项,其他数据不再有意义,那么他们应该以一个对象的形式存在。
  4. 一旦拥有新对象,你就有机会让程序散发出一种芬芳。可以将适当的程序行为移至新类。不必太久,所有的类都将在他们的小小社会中发挥价值。

九. Primitive Obsession(基类类型偏执)

  1. 大多数编程环境都有两种数据:结构类型允许你将数据组织成有意义的形式;基本类型则是构成结构类型的积木块。
  2. 对象技术的新手通常不愿意在小任务上运用小对象——像是结合数值和币种money类、由一个起始值和一个结束值组成的range类、电话号码或邮政编码等的特殊字符串。
  3. 你可以运用Replace Data Value with Object(以对象取代数据值)将原本单独存在的数据值替换为对象,从而走出洞窟,进入炙手可热的对象世界。
  4. 如果想要替换的数据值是类型码,而它并不影响行为,则可以运用Replace Type Code with Class(以类取代类型码)将它换掉。
  5. 如果你有与类型码相关的条件表达式,可运用Replace Type Code with Subclass(以子类取代类型码)或Replace Type Code with State/Strategy(以State/Strategy取代类型码)加以处理。
  6. 如果你有一组应该总是被放在一起的字段,可运用Extract Class(提炼类)。
  7. 如果你在参数列中看到基本类型数据,不妨试试Introduce Parameter Object(引入参数对象)。
  8. 如果你发现自己正从数组中挑选数据(数组中的元素各自代表不同的东西),可运用Replace Array with Object(以对象取代数组)。

十. Switch Statements(Switch惊悚现身)

  1. 面向对象程序的一个最明显特征就是:少用switch语句。从本质上说,switch语句的问题在于重复。面向对象中的多态概念可为此带来优雅的解决办法。
  2. 使用Extract Method(提炼函数)将switch语句提炼到一个独立函数中,再以Move Method(搬移函数)将它搬移到需要多态性的那个类里。
  3. 你必须决定是否使用Replace Type Code with Subclass(以子类取代类型码)或Replace Type Code with State/Strategy(以State/Strategy取代类型码)。一旦这样完成继结构之后,你就可以运用Replace Conditional with Polymorphism(以多态取代条件表达式)了。
  4. 如果你只是在单一函数中使用switch语句,多态就有点杀鸡用牛刀了。这种情况下Replace Parameter with Explicit Methods(以明确函数取代参数)是个不错的选择。如果你的选择条件之一是null,可以试试Introduce Null Object(引入null对象)。

十一. Parallel Inheritance Hierarchies(平行继承体系)

  1. Parallel Inheritance Hierarchies(平行继承体系)其实是Shotgun Surgery(散弹式修改)的特殊情况。在这种情况下,每当你为某个类添加一个子类,必须也为另一个类相应增加一个子类。
  2. 让一个继承体系的实例引用另一个继承体系的实例。如果再接再厉运用Move Method(搬移函数)和Move Field(搬移字段),就可以将引用端的继承体系消弭于无形。

十二. Lazy Class(冗赘类)

  1. 你创建的每一个类,都得有人去维护它。如果一个类的所得不值其身价,就应该消失。
  2. 如果某些子类没有做足够的工作,试试Collapse Hierarchy(折叠继承体系)。
  3. 对于几乎没用的组件,你应该以Inline Class(将类内联化)对付他们。

十三. Speculative Generality(夸夸其谈未来性)

  1. 当有人说“噢,我想我们总有一天需要做这事”,并企图以各式各样的钩子和特殊情况来处理一些非必要的事情,这种坏味道就出现了。
  2. 如果所有装置都会被用到,那就值得那么做;如果用不到,就不值得。用不上的装置只会挡你的路,所以,把它搬开吧。
  3. 如果你的某个抽象类其实没有太大作用,请运用Collapse Hierarchy(折叠继承体系)。
  4. 不必要的委托可运用Inline Class(将类内联化)除掉。
  5. 如果函数的某些参数未被用上,可对它实施Remove Parameter(移除参数)。
  6. 如果函数名称带有多余的抽象意味,应该对它实施Rename Method(函数改名),让它更现实一些。

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

  1. 有时你会看到这样的对象:其内某个实例变量仅为某种特定情况而设。这样的代码让人不宜理解,因为你通常认为对象在所有时候都需要它的所有变量。
  2. 请使用Extract Class(提炼类)给这个可怜的孤儿创造一个家,然后把所有和这个变量相关的代码都放进这个新家。
  3. 或许你还可以使用Introduce Null Object(引入Null对象)在变量不合法的情况下创建一个Null对象,从而避免写出条件式代码。
  4. 如果类中有一个复杂算法,需要好几个变量,实现者不希望传递一长串参数,所以他把这些参数放进字段中,导致坏味道。这些字段只在使用该算法时才有效,你可以利用Extract Class(提炼类)把这些变量和其相关函数提炼到一个独立类中。提炼后的新对象将是一个函数对象。

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

  1. 如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象……这就是消息链。
  2. 你应该使用Hide Delegate(隐藏“委托关系”)。
  3. 先观察消息链最终得到的对象时用来干什么的,看看能否以Extract Method(提炼函数)把使用该对象的代码提炼到一个独立函数中,再运用Move Method(搬移函数)把这个函数推入消息链。

十六. Middle Man(中间人)

  1. 对象的基本特征之一就是封装——对外部世界隐藏其内部细节。封装往往伴随委托。
  2. 人们可能过度运用委托。你也许会看到某个类接口有一半的函数都委托给其他类,这样就是过度运用。
  3. 这时应该使用Remove Middle Man(移除中间人),直接和真正负责的对象打交道。
  4. 如果这样“不干实事”的函数只有少数几个,可以运用Inline Method(内联函数)把他们放进调用端。
  5. 如果这些中间人还要其他行为,可以运用Replace Delegation with Inheritance(以继承取代委托)把它变成实责对象的子类,这样你既可以扩展原对象的行为,又不必负担那么多的委托动作。

十七. Inappropriate Intimacy(狎昵关系)

  1. 有时你会看到两个类过于亲密,花费太多时间去探究彼此的私有成分。如果这发生在两个“人”之间,我们不必做卫道士;但对于类,我们希望他们严守清规。
  2. 可以采取Move Method(搬移函数)和Move Field(搬移字段)帮他们划清界限,从而减少狎昵关系。
  3. 你也可以看看是否可以运用Change Bidirectional Association to Unidirectional(将双向关联改为单向关联),让其中一个类对另一个斩断情丝。
  4. 如果两个类实在是情投意合,可以运用Extract Class(提炼类)把两者共同点提炼到一个安全地点,让他们坦荡地使用这个新类。或者也可以尝试运用Hide Delegate(隐藏“委托关系”)让另一个类来为他们传递相思情。
  5. 继承往往造成过度亲密,因为子类对超类的了解总是超过后者的主观愿望。如果你觉得是该让这个孩子独自生活了,请运用Replace Inheritance with Delegation(以委托取代继承)让它离开继承体系。

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

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

十九. Incomplete Library Class(不完美的库类)

  1. 复用常被视为对象的终极目的。许多编程技术都建立在程序库的基础上。
  2. 库类构筑者没有未卜先知的能力,我们不能因此责怪他们。库往往构造的不够好,而且往往不可能让我们修改其中的类使它完成我们希望完成的工作。
  3. 如果你只想修改库类的一两个函数,可以运用Introduce Foreign Method(引入外加函数);如果想要添加一大堆额外行为,就得运用Introduce Local Extension(引入本地扩展)。

二十. Data Class(幼稚的数据类)

  1. 幼稚的数据类是指:他们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。
  2. 这样的类只是一种不会说话的数据容器,他们几乎一定被其他类过分细锁地操纵着。
  3. 你应该运用Encapsulate Collection(封装集合)把他们封装起来。对于那些不该被其他类修改的字段,请运用Remove Setting Method(移除设值函数)。
  4. 找出这些取值/设值函数被其他类运用的地点。尝试Move Method(搬移函数)把那些调用行为搬移到Data Class(幼稚的数据类)来。如果无法搬移整个函数,就运用Extract Method(提炼函数)产生一个可被搬移的函数。不久以后你就可以运用Hide Method(隐藏函数)把这些取值/设置函数隐藏起来了。
  5. Data Class(幼稚的数据类)就像小孩子。作为一个起点很好,但若要让它们像成熟的对象那样参与整个系统的工作,它们就必须承担一定责任。

二十一. Refused Bequest(被拒绝的遗赠)

  1. 子类应该继承超类的函数和数据。但如果他们得到所有礼物,却只从中挑选几样来玩!又该怎么办呢?
  2. 按传统做法,你需要为这个子类新建一个兄弟类,再运用Push Down Method(函数下移)和Push Down Field(字段下移)把所有用不到的函数从超类下推给那个兄弟。这样,超类就只持有所有子类共享的东西。
  3. 我们不建议你胡乱修改继承体系,应该运用Replace Inheritance with Delegation(以委托取代继承)来达到目的。

二十二. Comments(过多的注释)

  1. 注释本身不是一种坏味道,事实上他们还是一种香味呢。
  2. 有时候,注释之所以存在乃是因为代码很糟糕。把注释当做除臭剂是一种坏味道。
  3. 很多时候,注释可以帮助我们找到代码的坏味道。找到坏味道之后,我们首先应该以各种重构手法把坏味道去除。完成之后我们常常会发现:注释已经变得多余了,因为代码已经清晰说明了这一切。
  4. 如果你需要注释来解释一块代码做了什么,试试Extract Method(提炼函数);如果函数已经提炼出来,但还是需要注释来解释其行为,试试Rename Method(函数改名);如果你需要注释说明某些系统的需求规格,试试Introduce Assertion(引入断言)。
  5. 当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余。
  6. 注释应该用来记述将来的打算、标记你并无十足把握的区域。你可以在注释里写下自己“为什么做某某事”。这类信息可以帮助将来的修改者,尤其是那些健忘的家伙。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微观技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. Duplicated Code(重复代码)
  • 二. Long Method(过长函数)
  • 三. Large Class(过大的类)
  • 四. Long Parameter List(过长参数列)
  • 五. Divergent Change(发散式变化)
  • 六. Shotgun Surgery(散弹式修改)
  • 七. Feature Envy(依恋情节)
  • 八. Data Clumps(数据泥团)
  • 九. Primitive Obsession(基类类型偏执)
  • 十. Switch Statements(Switch惊悚现身)
  • 十一. Parallel Inheritance Hierarchies(平行继承体系)
  • 十二. Lazy Class(冗赘类)
  • 十三. Speculative Generality(夸夸其谈未来性)
  • 十四. Temporary Field(令人迷惑的暂时字段)
  • 十五. Message Chains(过度耦合的消息链)
  • 十六. Middle Man(中间人)
  • 十七. Inappropriate Intimacy(狎昵关系)
  • 十八. Alternative Classes with Different Interfaces(异曲同工的类)
  • 十九. Incomplete Library Class(不完美的库类)
  • 二十. Data Class(幼稚的数据类)
  • 二十一. Refused Bequest(被拒绝的遗赠)
  • 二十二. Comments(过多的注释)
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档