重构,体现一个工程师的基本素养和底蕴(细节篇)

重构小记(重构,改善既有代码的设计读后总结)


方法级别

提炼函数:

将一个大方法,拆成多个小方法,难点在于小方法的命名。
假如有早上上学的一个大方法,
那么就应该在里面有起床,穿衣服,吃早点等小方法,
而起床这个方法又可以分为穿衣服,穿鞋,叠被子等方法,
而穿衣服又有穿内衣,穿外衣等步骤。

内联函数

一个太小的方法,里面只做了一个动作,就可以考虑去掉了,去掉会使代码更清晰。
感觉内联函数说的就是不要过度的提炼函数。

内联临时变量

就是说要尽量去掉只用一次的临时变量
注意:如何判断临时变量只被赋值了一次,就是在前面加flnal,能编译过就是只有一次
比如有代码:int age = user.getAge();return age>18;
那么这两句代码,就完全可以合成一句:return user.getAge()>18;

以查询取代临时变量

如果临时变量被用了多次的话,也尽量用方法去代替临时变量。
比如有代码:
int sum = a + b;
if(sum>10){
return sum2;
}
else{
return sum
3;
}
这里的sum变量,就要用方法封装后去掉。变成以下:
int getSum(){
return a + b;
}
if(getSum()>10){
return getSum()*2;
}
else{
return getSum()*3;
}
这种不一定就是最正确的,甚至有时候会反过来改,所以把握整体很关键。
这样可能会对性能产生一点影响,但几乎也可以忽略不计。

引入解释型变量

用变量名来简化复杂表达式
比如有代码:if(a > 0 && aa > 0 && aaa> 0 && b < 0 && bb < 0 && bbb < 0){return true;}
这个if中的判断表达式就太复杂,可改成下面这样:
boolean aClassMoreThanZero = a > 0 && aa > 0 && aaa> 0;//a类变量大于0
boolean bClassLessThanZero = b < 0 && bb < 0 && bbb < 0;//b类变量小于0
if(aClassMoreThanZero && bClassLessThanZero){return true;}

分解临时变量

临时变量被赋值多次,并且是不同的意思,就要将临时变量分解成多个变量,保证一个临时变量只代表一个意思。

移除对参数的赋值

一个方法中,尽量不要对入参进行赋值,可以尝试将入参用final修饰

以函数对象取代函数

如果方法中局部变量太多,就创建一个函数对象,把所有的局部变量变成这个对象的属性,然后把这个方法的实现全部移到对象中
说实话,我个人觉得这种方式没什么用,因为如果你有时间这样搞,还不如换个思路梳理下这个方法,不过也可能是我没遇到这样情况。

替换算法

替换一个方法的实现,重点在于替换前后要保证执行结果一致。
比如有个方法,是冒泡算法实现排序,那么就可以用插入排序来替代冒泡排序。


对象级别

搬移函数

一个类中有个函数,却与另一个类进行更多的交流,那么就尝试将这个函数整体迁移过去。

搬移字段

一个类中有某个字段,被另一个类更多的用到,那么就尝试将这个字段移到另一个类中。

提炼类

如果一个类越来越大,就可以考虑将其中的一部分与类名关系不大的属性抽出来,做成这个类的子类。

将类内联化

与提炼类相反,如果一个类没有做太多事情,就可以尝试将他与另一个类合并。

隐藏委托关系

隐藏真实的调用关系,将真实的调用关系封装在最外层的方法中。
比如有个A类,A中有个B类属性,B中又有个C类。
如果想通过A获得C的值,普通的做法就是a.getB().getC();但是这样就把真实的C暴露出来了,其实调用方也根本没必要知道这个,这时候就可以这样做:
在A类中添加一个方法,叫getC(),方法的实现就是return b.getC();这样就相关与把B给隐藏了;
这时如果再想通过A获得C,那么就可以直接a.getC();这就是隐藏委托关系。

移除中间人

这个是隐藏委托关系的反向重构,也就是说当委托的太多的时候,就应该考虑是不是应该直接点,物极必反。
相当于把上个例子的a.getC()又改成a.getB().getC();

引入外加函数

对于有些引入的源码类,如果你想增强它,肯定不能直接修改源码,那么就需要引入外加函数。
感觉这个就是对源码的增强,通过新建一个函数,返回增强后的内容,以后使用的时候就直接用这个函数就好了。

引入本地扩展

这个是引入外加函数的增强版
如果需要对源码进行很多增强,那么就可以这样做
方案一:新建一个类(子类),继承源码类,新增强的功能就可以放在子类中
方案二:新建一个类(包装类),将源码类当成这个新类的属性,那么也可以完成和方案一一样的效果


重新组织数据

自封装字段

属性私有化,通过get/set方法取值/设置值,而不是直接对值进行操作

以对象取代数据值

属性使用对象,这是一个过程,比如刚开始是String,后面慢慢的感觉还要一些字段,而这些字段又是一类数据,那么就将这一类数据抽成一个类来代替原来分散的数据。

将值对象改为引用对象

将一个类衍生出的彼此相同的许多实例,使用工厂替换成同一个实例,并将原类的构造方法私有化,禁止直接构造。
比如员工是个类,类中有个属性是老板这个类,那么多个员工的老板,其实就是同一个,这时候就要用这种方式,先将老板类的构造方法私有化。
然后可以先在静态块中将老板类创建好,然后每new一个新员工的时候,老板类的赋值就用这个创建好的老板实例就行。

将引用对象改为值对象

如果引用对象太小,且不可变,难以管理,就可以考虑将上面的方式反过来了

以对象取代数组

如果数组中有多个不同的类型,复杂到不能让人很快的明白哪个是什么意思,就该用对象来代替了
比如数组=[“小明”,20,175,70];不是很容易就看出来这个数组中每个的意思,这时就要用对象代替,那么属性就分别是名字,年龄,身高,体重。

以字面常量取代魔法值

任何一个数字,都不应该凭空出现,需要在类前将它设置成常量,然后再去引用,这样才能使代码变的清晰易懂。

以类取代类型码

1、2、3、4等类型码,不能很清楚的表达意思,这时候就应该用类来代替这些类型码。
Lei.A.getCode(),代替原来的1;
Lei.B.getCode(),代替原来的2;…

以子类取代类型码

类中多个类型码,可以使用子类,每个子类对应一个类型码。
使用的时候,根据不同的条件,原来是返回不同的类型码,现在改为返回不同的子类。

以state/Strategy取代类型码

如果类由于其他原因,不能被继承,那么就需要新建一个状态类,然后不同的子类去继承这个状态类,还是一个状态对应一个子类。
然后这个状态类中建立一个查询方法,返回状态码,各个子类重写这个方法,返回具体的状态码。

以字段代替子类

如果子类差别不大,甚至只是在返回常量数据不一样,那么就要考虑删掉子类了,原子类的功能,完全可以通过不同的方法来代替。


简化条件表达式

分解条件表达式

表达式太多,就会分不清每个的意思,这时候就应该把表达式按照意思区分成小块,每个小块通过命名来区分具体的意思。

合并条件表达式

如果多个表达式,返回同一个结果,就要考虑合并成一个表达式。

合并重复的条件片段

如果每个表达式的分支上都有着相同的代码块,那么这就是重复代码,我们就应该把这块代码提炼出来。

移除控制标记

就是类似于if(怎么样)就停止 这样类似的控制标记,可以用break,continue,return等替代,多用在循环体中。

以卫语句取代嵌套的条件表达式

尽量避免嵌套的条件表达式
比如if(){}else{if(){}else{if(){}}}
尽量改成if(){};if(){};if(){};…

以多态取代条件表达式

将条件表达式的每个分支放在每个子类重写的方法中,原始方法变抽象
多态就是根据对象的不同类型采取不同的行为。

引入断言

断言是一个条件表达式,应该总是为真,如果失败,则说明程序员犯了错误。
Assert类有很多方法,常用的是Assert.isTrue(XXX);
断言不会改变程序任何行为,但能帮助程序员理解代码正确运行的必要条件。


简化函数的调用

函数改名

函数的名字至关重要,代码首先是为人写的,其次才是为机器写的

添加参数

添加参数要谨慎,实在没办法的情况下,才会考虑添加参数。

去除参数

如果某个参数确实没用了,记得赶紧去掉,不要抱着不去也不会出问题的心态,否则这样只会让程序越来越复杂。
去除参数的时候,还要注意多态的影响。

将查询函数和修改函数分离

一个函数只完成一个职责,要么查询,要么修改。

另函数携带参数

统一合并类似功能的函数
如果多个函数只是部分值不一样,做的确实一类事情,那么就要考虑合并成一个函数,并把不同的值当成参数传进来。

以明确函数去掉参数

与上面的相反,如果一个函数中根据入参不同,执行的逻辑已经完全不同了,那么就将他们分开成各自的函数。

保持对象完整性

如果入参是某个对象的多个属性时,考虑用这个对象整体作为入参,来代替多个参数。

以函数取代参数

如果函数的参数可以通过函数内部通过其他方法得到,就不要使用参数来传递。
尽量的简化函数的参数,因为太多的参数会让函数变的复杂。

引入参数对象

如果一些参数总是很自然的同时出现,那么就要考虑是不是用一个对象来代替这些参数了

移除设值函数

如果一个字段在函数过程中需要被修改,就为它提供一个修改的函数
如果这个字段在函数过程中不会被修改,就删掉修改它的函数,同时用final修饰。

隐藏函数

一个函数从没有被其他类用到,就用private修饰它

以工厂函数取代构造函数

如果创建对象时,需要根据不同的条件创建不同的对象,可以考虑写个工厂方法来代替构造方法

封装向下转型

函数的返回值,不要设置成Object等原始类型,最好指定类型,免得调用方又要强制转型。

以异常取代错误码

就是自定义异常,并合理的利用它

以测试取代异常

就是方法一开始,要对入参进行校验,看是否是后面程序可以接受的参数
比如如果为空,要怎么样,如果为0,要怎么样,这些能想到的要提前抓到并做合理的处理。最常见的就是判空。


处理概括关系(继承关系)

字段上移

子类有相同作用的字段,应该上移到他们的父类中。

字段下移

与上面相反,如果父类的某些字段,只与部分某些子类有关,那么就应该将这些字段下移到对应的子类中。

函数上移

子类中有相同作用的函数,应该将这个函数上移到他们的父类中。

函数下移

与上面相反,如果父类的某些函数,只与部分某些子类有关,那么就应该将这些函数下移到对应的子类中。
####构造函数本体上移
各个子类中的一些构造函数,如果他们的本体几乎完全一致,那么就在父类中建一个构造函数,然后在子类中调父类,类似于super(id,name)

提炼子类

如果类中的某些属性,只被某些实例用到,那么就新建个子类,将这些属性移到子类中。
这样保证不用这些属性的实例,用父类new;用到这些属性的实例,用子类new。

提炼超类

如果两个类有相似的特性,就新建一个他们的父类,将相同特性移到父类上去,避免代码的重复。

提炼接口

若干类中有相同的子集,可以考虑新建接口让这些类继承,然后统一相同的部分,调用也改成接口调用

折叠继承体系

如果子类和父类没有太大区别,就将他们合为一体

塑造模板函数

详见设计模式的模板模式,就是在抽象父类中定义好抽象函数,再写个方法A,写明各个抽象函数的调用顺序。
那么子类中只需要重写父类的抽象方法,用的时候,子类调用方法A,就能按照父类方法A中的调用顺序来调用各自的实现了。

以委托取代继承

如果父类中的很多属性和方法,子类都用不到,那么就可以放弃继承,在子类中引入一个原父类的属性引用即可。

以继承取代委托

与上面的相反,如果两个类之间的委托调用的太多,甚至几乎是全部了,那么就该考虑让委托的这个类作为父类了,之间继承会更好。


大型重构

梳理并分解继承体系

某个继承体系同时承担两项责任或多项责任时,考虑拆开。
比如原来有父类A,A有两个子类B和C,B还有个子类D,C还有个子类E。B和C都承担同类责任,而D和E却承担另外一个不同的责任。
此时就应该把BC和DE分开了。我们建立两个继承体系A和F,A拥有子类BC,F拥有子类DE,通过委托关系(A作为F的属性)让其中一个F调用另外一个A。

将过程化设计转为对象设计

其实就是尽量的面向对象,将面向过程变成转成面向对象编程。

将领域和表述/显示分离

与界面展示相关的逻辑的类,和纯业务逻辑的类,要分离。

提炼继承体系

一个类在演变过程中,所承担的责任越来越多时,就应该考虑分离它了,可以采取抽出一部分当成子类,使整体变成继承的体系。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499911.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MVPVM模式介绍

一、概述MVPVM即&#xff1a;Model-View-Presenter-ViewModel。此模式是MVVM和MVP模式的结合体。但是交互模式发生了比较大的变化。MVVM参考本博客文章&#xff1a;iOS-MVVM-模式介绍MVP参考本博客文章&#xff1a;MVP模式介绍 二、原理&#xff1a;Presenter同时持有View、Mod…

[线程池] ------ 形象的描述线程池,用一个特好记的例子来记忆

线程池 为了减少线程频繁的创建和销毁过程&#xff0c;引入池的概念。 将一些线程先创建好放在线程池中&#xff0c;每次来任务就用池中的线程执行&#xff0c;空闲时池中线程就等待&#xff0c;但不销毁。 原始线程池的创建&#xff1a; ThreadPoolExecutor executor1 new …

分组密码的工作模式

一、理论基础1.概述密码学中&#xff0c;块密码的工作模式允许使用同一个块密码密钥对多于一块的数据进行加密&#xff0c;并保证其安全性。块密码自身只能加密长度等于密码块长度的单块数据&#xff0c;若要加密变长数据&#xff0c;则数据必须先被划分为一些单独的密码块。通…

数据仓库基本认知

数据仓库概念&#xff1a; 数据仓库&#xff0c;英文名称Data Warehouse&#xff0c;简写为DW。 是一种面向分析的存储系统。 他是一个很大的数据存储集合&#xff0c;出于企业的分析性报告和决策支持目的而创建&#xff0c;对多样的业务数据进行筛选与整合。 它为企业提供一…

PBOC3.0中使用的国密SM2算法

一、知识准备 PBOC3.0规范就是《中国金融集成电路&#xff08;IC&#xff09;卡规范》3.0版本。SM2是国密局推出的一种他们自己说具有自主知识产权的非对称商用密码算法。本身是基于ECC椭圆曲线算法的&#xff0c;所以要讲SM2, 先要弄懂ECC。 完全理解ECC算法需要一定的数学功底…

mapper注入失败,NoSuchBeanDefinitionException: No qualifying bean of type [com.xxx.XxxMapper] found for d

mapper注入失败: Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.xxx.XxxMapper] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependen…

战地体能训练模拟器

一、名称&#xff1a;战地体能训练模拟器二、整体介绍&#xff1a; 1.体验者身处一个封闭空间的正中央&#xff0c;空间内部表面全部附着显示器&#xff0c;包含地板(因为地板是平的&#xff0c;可以使用投影的方式实现)&#xff0c;经过视角上的设计&#xff0c;体验者就像身处…

java读文件写文件

使用了try-with-resource语法&#xff08;JDK1.7及以上&#xff09;&#xff0c;代码更加便捷 Junit测试样例&#xff0c;先写入文件&#xff0c;再读出来&#xff1a; RunWith(SpringJUnit4ClassRunner.class) ContextConfiguration(locations "classpath:spring-conf…

Markdown入门

Markdown 是一种轻量级的「标记语言」&#xff0c;它的优点很多&#xff0c;目前也被越来越多的写作爱好者&#xff0c;撰稿者广泛使用。看到这里请不要被「标记」、「语言」所迷惑&#xff0c;Markdown 的语法十分简单。常用的标记符号也不超过十个&#xff0c;这种相对于更为…

bean注入失败的几种情况和解决思路:NoSuchBeanDefinitionException: No qualifying bean of type

bean注入失败&#xff0c;无非是两种情况&#xff0c;要么注入的写法出错&#xff0c;要么被注入的Bean未定义。 1.Bean未定义 如果是直接在XML中配置bean标签的时候 检查id和class是否写对 如果是用注解形式申明Bean 先检查Controller、Service、Repository、 Component …

通过AVFoundation框架获取摄像头数据

一、概述 从iOS4开始&#xff0c;AVFoundation框架增加了几个类&#xff0c;AVCaptureDevice、AVCaptureSession等&#xff0c;可以获取摄像头的数据&#xff0c;而不会弹出类似于ImagePicker一样的界面&#xff0c;我们可以将数据转为一张张的图片&#xff0c;然后我们可以即时…

mysql数据库支持emoji表情的详解

mysql存储emoji表情的时候&#xff0c;就会报错&#xff0c;如下&#xff1a; Error updating database. Cause: java.sql.SQLException: Incorrect string value: ‘\xF0\x9F\x98\x8A\xF0\x9F…’ for column ‘这是我表中的字段’ at row 1 初步定位是我的数据库是utf8编码…

CoreText使用介绍

一、概述 1.CoreText是苹果创建的一个用于文字排版的框架&#xff0c;可以实现文字排版、图文混排等复杂的界面效果。从iOS3.2启用。2.一个开源工具类-OHAttributedLabel&#xff0c;就是使用CoreText框架实现的&#xff0c;能够实现一个Label中有不同的文字大小、文字颜色、字…

编程规范:长函数的思考

在工作&#xff0c;我们应该都不想看到非常的长函数。对于一个运行5年左右的项目&#xff0c;极有可能出现这种情况。由于长函数的长、if/else嵌套&#xff0c;导致代码的可读性非常差&#xff0c;这对于项目的维护和开发带来了极大的困难。所以我们应该避免写长函数&#xff0…

用redis实现延迟队列

现在在用的redis实现延迟队列的主流程

iPhone各版本屏幕尺寸

设备宽高对角线逻辑分辨率(point)Scale Factor设备分辨率(pixel)PPI iPhoneWidthHeightDiagonal 3GS2.4 inches (62.1 mm)4.5 inches (115.5 mm)3.5-inch320x4801x320x480163 4(s)2.31 inches (58.6 mm)4.5 inches (115.2 mm)3.5-inch320x4802x640x960326 5c2.33 inches (59.2…

maven更新快照不起作用的解决方法

问题&#xff1a;maven的快照包更新后&#xff0c;调用方使用idea点下面这个地方更新maven&#xff0c;并没有拉到最新的快照 解决方法1 删除本地仓库的快照包&#xff0c;再重新拉一次 解决方法2 下图&#xff0c;这里点进去 下图&#xff0c;这个勾上就行了&#xff0c;再…

iOS中frame和Bounds之间的区别

frame frame是每个view必备的属性&#xff0c;代表的是当前视图的位置和大小&#xff0c;没有设置他&#xff0c;当前视图是看不到的。位置需要有参照物才能确定&#xff0c;数学中我们用坐标系来确定坐标系中的某个点的位置&#xff0c;iOS中有他特有的坐标系&#xff0c;如下…

[数据库]-----mysql数据的冷热分离 第二版

1.前提 这次数据库的冷热分离算是第二次做了 其实之前已经做过一次冷热分离了,涉及到数据库复制时,当时是趋近于业务的(后面会详细讲),整体来讲不是很好用,这次算是重构了吧 做的最终结果还是和前一次一样: 数据库中的订单数据,是每时每刻都在增加 我们认为3个月以内的数据,用…

URL结构

一、概念 URL&#xff1a;统一资源定位符 (Uniform Resource Locator, URL)。 完整的URL由这几个部分构成&#xff1a;scheme://host:port/path?query#hash&#xff1a; scheme&#xff1a;通信协议&#xff0c;常用的有http、https、ftp、mailto等。 host&#xff1a;主机…