重构-坏代码的味道

代码的坏味道

何时必须重构?没有任何标准能比得上一个见识广博者的直觉。而某些迹象,则会指出“这里有可以用重构解决的问题”,一共22条坏代码味道。

Duplicated Code(重复代码)

如果你在一个以上的地点看到相同的程序结构,那么可以肯定,将它们合而为一,程序会变得更好。

最单纯的重复代码就是,同一个类的两个函数含有相同的表达式。这时需要采用Extract Method(提取方法)提取重复代码。

另一个常见的情况是,同个父类的两个子类内含有相同表达式。这是需要使用Extract Method(提取方法),然后使用Pull Up Method(方法上移),推入父类。

如果代码只是类似,并非完全形同,那么就得运用Extract Method(提取方法) 将相似部分和差异部分分割开,单独构成一个函数。

然后可以运用Form Template Method(表单模板方法)获得一个模板方法设计模式。

如果有些函数以不同的算法做相同的事,你可以选择其中较清晰的一个,并用Substitute Algorithm(替换算法)将其他函数替换掉

如果两个毫不相关的类出现Duplicated Code(重复代码),应该考虑对其中一个使用Extract Class(提炼类型),将复制代码提炼到一个独立类中,然后在另一个类使用这个新类。

但是重复代码所在的函数可能的确属于某个类,另一个类只能调用它。你必须决定这个函数放在那里最合适。

Long Method(过长函数)

程序越长越难理解。小函数容易理解的真正关键在于一个好名字,读者可以通过名字了解函数的作用。

最终效果是:应该更积极地分解函数。每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写在一个独立函数中,并以其用途命名。

大部分场合中,要把函数变小,只需要使用Extract Method(提炼函数)。找到函数中适合集中在一起的不分,把它们提炼出来形成一个新函数。

如果函数内有大量的参数和临时变量,他们会对你的函数提炼形成障碍。如果使用提取方法,会把变量当做参数传递。

此时,可以运用Replace Temp with Query(以查询取代临时变量)来消除这些临时元素。

Introduce Paramter Object(引入参数对象)和Preserve Whole Object(保持对象完整)可以将过长的参数列变得更简洁一些。

如果上诉方法无效,就应该使用 Replace Method with Method Object(以函数对象代替函数)

如何确定该提炼那一段代码?

一个很好的技巧就是寻找注释。通常能指出代码用途和实现手法之间的语义距离。

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

就算只有一行代码,如果它需要以注释来说明,那也值得将它提炼到独立函数去。

条件表达式和循环常常也是提炼的信号,可以使用Decompose Conditional(分解条件表达式)处理条件表达式。可以将循环和其内的代码提炼到一个独立函数中。

Large Class(过大的类)

如果利用单个类做太多事情,其内往往就会出现太多实例变量。Duplicated Code(重复代码)也就接踵而至。

可以运用Extract Class(提炼类型)将几个变量一起提炼到新类内。提炼时应选择类内彼此相关的变量。

通常如果类内的数个变量有着相同的前缀或字尾,就有机会把它们提炼到某个组件内。

如果这个组件适合作为一个子类,Extract Subclass(提炼子类)会比较简单。

有时候并非所有时刻都使用所有实例变量。那么可以多次使用Extract Class(提取类)或Extract Subclass(提取子类)。

Long Parameter List(过长参数列)

不必把函数需要的所有东西以参数传递,只需要给足够的,让函数能从中获取自己需要的东西就可以了。

如果向已有的对象发出一条请求就可以取代一个参数,那么应该使用Replace Parameter with Method(以函数代替参数)。

还可以运用Preserve Whole Object(保持对象完整)将来自同一个对象的一堆数据收集起来。

如果某些数据缺乏合理的对象归属,可以使用Interduce Parameter Object(引入参数对象)为它们制造一个参数对象。

如果你不希望造成“被调用对象”与“较大对象”间的某种依赖关系。使用参数也合情合理。

Divergent Change(发散式变化)

一旦需要修改,我们希望能够跳到系统的某一点,只在该出做修改。如果不能做到这点,你就嗅出两种紧密相关的刺鼻中的一种了。

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

同一个类中,如果新加一个数据库,需要修改三个函数。新家一个工具,修改四个函数。那么将这个对象分成两个会更好。每个对象可以只因一种变化而需要修改。

针对某一外界变化的所有相应修改,都只应该发生在单一类中,而这个新类内所有内容都应该此变化。应该找出所有变化,然后使用Extract Class(提取类)

Shotgun Surgery(霰弹式修改)

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

如果需要修改的代码散布四处,你不但很难找到他们,也很容易忘记某个重要的修改。

这种情况下应该使用Move Method(搬移函数)和Move Field(搬移字段)把所有需要修改的代码放进同一个类。如果没有就创建一个。

Divergent Change和Shotgun Surgery区别

Divergent Change是指“一个类受多种变化的影响”,Shotgun Surgery是指“一种变化引发多个类相应修改”。

这两种情况你都会希望整理代码,使“外界变化”与“需要修改的类”一一对应。

Feature Envy(依恋情结)

函数对某个类的兴趣高过对自己所处类的兴趣。使用Move Method(搬移函数)把它移到该去的地方。称为依恋情结。

函数中只有一部分受依恋情结之苦,这时候应该使用Extract Method(提取方法)把这一部分提炼到独立函数中,在使用Move Method(搬移函数)

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

策略模式和访问者模式,就是为了对抗Divergent Change(发散式变化)。原则:将总是一起变化的东西放在一块。

Data Clumps(数据泥团)

常常可以在很多地方看到相同的三四项数据:两个类中相同的字段、函数签名中相同的参数。总是绑在一起出现的数据应该拥有它自己的对象。

找出这些数据以字段形式出现的地方,运用Extract Class(提取类)将他们提炼到一个独立对象中。然后将注意力转移到函数签名上。

运用Introduce Parameter Object(引入参数对象)或Preserve Whole Object(保持对象完整性)为他瘦身。

这么做的好处是可以将很多参数列缩短,简化函数调用。

Primitive Obsession(基本类型偏执)

可以运用Replace Data Value with Object(以对象取代数据值)将原本单独存在的数据值替换为对象。

如果想要替换的数据值是类型码,而他不影响行为,可以运用Replace Type Code with Class(以类取代类型码)替换掉。

如果你有与类型码相关的条件表达式,可以运用Replace Type Code with Subclasses(以子类取代类型码)或 Replace Type Code with State/Strategy(以状态模式/策略模式取代类型码)加以处理。

如果你有一组总是被放在一起的字段,可运用Extract Class(提炼类)。如果你在参数列中看到基本型数据,可运用Introduce Parameter Object(引入参数对象)。

如果你发现自己正从数组中挑选数据,可运用Replace Array with Object(以对象取代数组)

Switch Statements(switch惊悚现身)

少用switch语句,问题在于重复。经常会发现同样的switch语句散布于不同地点。如果要为它添加一个新的case,就必须找到所有的switch语句并修改它们。

使用面向对象中的多态概念可解决此方法。大多数时候,看到switch语句,就应该考虑以多态来替换它。

switch语句常常根据类型码进行选择。所以应该使用Extract Method(提炼函数)将switch语句提炼到一个独立函数中,再以Move Method(搬移函数)将它搬移到需要多态性的那个类里。

此时你必须决定是否使用Replace Type Code with Subclasses(以子类取代类型码)或Replace Type Code with State/Strategy(以状态模式/策略模式取代类型码)。

一旦完成继承结构后,就可以运用Replace  Conditional with Plomorphism(以多态取代条件表达式)。

如果只是在单一函数中使用,并且不想改动它们。可以使用Replace Parameter with Explicit Methods(以明确函数取代参数)。

如果选择条件之一是null,可以使用Introduce Null Object(引入Null对象)

Parallel Inheritance Hierarchies(平行继承体系)

平行继承体系是霰弹式修改的特殊情况。在这种情况下每当你为某个类添加一个子类,也必须为另一个类添加相应的子类。

如果你发现某个继承体系的类名称前缀和另一个继承体系的类名称前缀完全相同,便是问到了这种坏味道。

消除这种重复性的策略是,让一个继承体系引用另一个继承体系。或者使用Move Method(搬移函数)和Move Field(搬移字段)。

Lazy Class(冗赘类)

如果某些子类没有做足够的工作,可以使用Collapse Hierarchy(折叠继承体系)。

对于几乎没有用的组件,可以使用Inline Class(将类内联化)

Speculative Generality(夸夸其谈未来性)

如果某个抽象类其实没有太大作用,请使用Collapse Hierarchy(折叠继承体系)。

不必要的委托可运用Inline Class(将类内联化)除掉。

如果函数的某些参数未被使用上,可使用Remove Parameter(移除参数)

如果函数的名称带有多余的抽象以为,应该对他实施Rename Method(函数改名)

如果函数或类的唯一用户是测试用例,这就是明显的Speculative Generality。需要将它和对应的测试用例一并删掉。

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

某个变量仅为某种特定情况而设。通常认为对象在所有时候都需要它的所有变量,在变量未被使用的情况下猜测当初其设置目的,会让你发疯。

如果类中有一个复杂算法,需要好几个变量。可以使用Extract Class(提炼类)还可以使用Introduce Null Object(引入Null对象)

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

如果向一个对象请求另一个对象,这就是消息练。如果长串的消息链,意味着客户代码与查找过程中的导航结构紧密耦合。

一旦对象发生变化,客户端就不得不做出相应修改。这时应该使用Hide Delegate(隐藏委托关系)。

先观察消息链最终得到的对象是用来干什么的,看看能否Extract Method(提炼函数),再用Move Method(搬移函数)推入消息链。

Middle Man(中间人)

如果看到某个类接口有一半的函数都委托给其他类,这样就是过度运用。这个时候应该Remove Middle Man(移除中间人)

如果这样的函数很少,可以使用Inline Method(内联函数)把他们放进调用段。如果这些中间人还有其他行为,可以使用Replace Delegation with Inheritance(以继承取代委托)

Inappropriate Intimacy(狎昵关系)

看到两个类过于亲密,话费太多时间去探究彼此的private成分。过分亲密的类,可以使用Move Method(搬移函数)和Move Field(搬移字段)。

也可以看看是否可以运用Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)

如果很难分割两个类,可以使用Extract Class(提炼类)把两者共同点提炼到寒泉地点。或者使用Hide Delegate(隐藏委托关系)

如果子类对父类的继承关系不深,可以使用Replace Inheritance with Delegation(以委托取代继承)

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

如果两个函数在做同一件事,却有着不同的签名,可以使用Rename Method(函数改名)根据用途重新命名。

请反复使用Move Method(搬移函数)将某些行为移入类,知道两者的协议一致位置。

如果必须重复移入代码才能完成这些,可以使用Extract Superclass(提炼超类)

Incomplete Library Class(不完美的库类)

如果只想修改库类的一两个函数,可以运用Introduce Foreign Method(引入外加函数)

如果想添加一大堆额外行为,就得运用Introduce Local Extension(引入本地扩展)

Data Class(数据类)

Data Class指拥有一些字段,以及用于访问的函数。除此之外一无长物。类似于model层。

如果这些类拥有public字段,应该运用Encapsulate Field(封装字段)

如果这些类内含有容器类的字段,应该检查是否进行了恰当的封装。如果没有就运用Encapsulate Collection(封装集合)

对于不该被其他类修改的字段,运用Remove Setting Method(移除设值函数)

然后找出这些函数被其他类调用的地点,尝试Move Method(搬移函数)把这些取值/设值隐藏起来

Refused Bequest(被拒绝的遗赠)

如果子类复用了父类的行为,却又不愿意支持父类的接口。这就意味着继承体系设计错误,需要新建一个子类,

然后使用Push Down Method(函数下移)和Push Down Field(字段下移)把所有用不到的函数下推给子类。

这样父类就只持有所有子类共享的东西。建议:所有父类都应该是抽象的

Comments(过多的注释)

如果你需要注释来解释一块代码做了什么,试试Extract Method(提炼函数)。

如果函数已经提炼出来了,但还是需要注释来解释其行为。试试Rename Method(函数改名)

如果你需要注释来说明某些系统的需求规格,试试Introduce Assertion(引入断言)

如果你不知道该做什么,这才是注释的良好运用动机

时间: 2024-10-11 10:31:37

重构-坏代码的味道的相关文章

高效重构 C++ 代码

引言 Martin Fowler的<重构:改善既有代码的设计>一书从2003年问世至今已有十几年时间了,按照计算机领域日新月异的变化速度,重构已经算是一门陈旧的技术了.但是陈旧并不代表不重要,恰恰随着演进式设计被越来越广泛的使用,重构技术已经被认为是现代软件开发中的一项必备的基本技能!所以今天在任何软件开发团队中,你都会不时听到或看到和重构相关的代码活动.然而对于这样一种被认为应该是如同“软件开发中的空气和水”一样的技术,在现实中却比比皆见对重构的错误理解和应用.首先是不知道重构使用的正确场合

后端系统性能优化(第一季:改掉那些坏代码)

我们核心业务系统的中心服务每天承载着上千万金额.几十万笔的订单量,在数据量高速增长,公司业务节节攀升的客观因素下,以及面对即将到来的6月份世界杯的流量\交易 高峰的压力,核心业务系统性能优化以及重构显得越发重要而又迫在眉睫. 时刻准备着 在进行性能优化之前,我们做了很多的准备工作,包括 压力测试,数据库sql提取,性能监控日志数据,请求量等数据的收集,分析整体的性能瓶颈,请求量的波动特点,数据库负载波动情况. 压力测试,几个部门通力的合作,把系统在极端并发的情况下所表现出来的性能瓶颈收集出来,分

Node.js(十三)——Promise重构爬虫代码

在重构代码之前,先要了解下什么是https? https协议:基于ssl/tls的http协议,所有的数据都是在 ssl/tls协议的封装之上传输的,也就是说https协议是在http协议基础上 添加了ssl/tls握手以及数据加密传输,因此这就是两者之间最大的区别. https模块专门处理加密访问的,区别在于搭建https服务器的时候需要有ssl证书. 模拟搭建https服务器 var https = require('https') var fs = require('fs')//文件系统模

AIDE支持实时错误检查、代码重构、代码智能导航、生成APK

AIDE是一个Android Java集成开发环境,可以在Android系统内进行Android软件和游戏的开发.它不仅仅是一个编辑器,而是支持编写-编译-调试运行整个周期,开发人员可以在Android手机或者平板机上创建新的项目,借助功能丰富的编辑器进行代码编写,支持实时错误检查.代码重构.代码智能导航.生成APK,然后直接安装进行测试.

后端系统性能优化(第一季 2 找出坏代码)

昨天开了个头 博文链接:后端系统性能优化(第一季:改掉那些坏代码) 今天,来说说 什么样的代码才是坏代码,怎么来找出这些坏代码. 不少猿在吐槽烂代码.但是我们今天说的不是烂代码,坏代码只需要改动很小的一部分,把它的坏的地方改掉,他依然是好代码 .而烂代码,只有重新写过了,才会让你觉得浑身轻松,压力瞬间释放,而且在写之前你还得花90%的时间去看懂它.所以我说改掉坏代码,因为只有坏代码才能改,而烂代码是用来看.我很庆幸我在的这个团队的代码驾驭能力都还不错,很少有烂代码.但为什么还会有坏代码?坏代码不

重构改善代码的既有设计

最近在学习重构改善代码的即有设计,虽然在平时的工作学习中有尝试进行重构,但没有清晰的思路往往就是随性而为,以个人的编码风格为准,我们往往知道这样会更好,但是面对编程风格的挑战时,我们往往拿不出准确专业的理论去说服别人遵循这项准则,而我们的想法最终也无疾而终,还是沦落成为个人英雄主义. 此博客不是为了阐述,仅因为还未完全熟练,需时时查看,但是在工作中往往书不在身边,无法翻阅时作为参考: 重新组织函数: Extract Method(提炼函数) Inline Method(内连函数) Inline

JAVAEE——BOS物流项目05:OCUpload、POI、pinyin4J、重构分页代码、分区添加、combobox

1 学习计划 1.实现区域导入功能 n OCUpload一键上传插件使用 n 将文件上传到Action n POI简介 n 使用POI解析Excel文件 n 完成数据库操作 n 使用pinyin4J生成简码和城市编码 2.区域分页查询 n 页面调整 n 服务端实现 3.重构分页代码 n BaseAction n 子类Action 4.分区添加功能 n 什么是分区 n 页面调整(combobox使用) n 服务端实现 2 实现区域导入功能 2.1 jquery OCUpload一键上传插件使用 O

黑客教父郭盛华:8种方法能快速重构整体代码库

中国黑客教父,元老级人物,威名远播的网络黑客安全专家,东方联盟创始人郭盛华提供了8种方法能快速重构整体代码库:他表示,虽然许多软件项目都是以最好的意图开始的,比如干净的架构,明确的目标和明确的目标,但并非所有的目标都是如此.而且,在那些做的人当中,并不是所有人都会一直这样. 随着时间,功能要求,财务压力,竞争优先级以及不断变化的开发人员,很可能开始作为代码质量的光辉典范最终变成一个庞然大物. 整体代码库本质上很难维护.这可能是由于许多原因,包括以下功能: 做太多了. 知道太多. 有太多的责任.

重构笔记——代码的坏味道(上)

在重构入门篇中,简单地介绍了重构的定义.为何重构.何时重构等.我想对于重构是如何运作的,你已经有了较好的理解了.但是对于代码中的坏味道,你可能 知道的并不多.坏味道可能是无形中产生的,也可能是开发人员偷懒造成的,还可能是其它某些因素导致的.不管怎么样,代码中的坏味道对程序没有半点好处,它 会促使程序腐烂,甚至变质.对于开发人员,真的是很有必要对这些坏味道进行了解和熟悉,理解它们产生的场景.针对当前程序,发现坏味道,对代码进行重构, 以消除坏味道,提高代码质量,提高自己水平. 下面让我们一起来熟悉