重构小记(重构,改善既有代码的设计读后总结)
方法级别
提炼函数:
将一个大方法,拆成多个小方法,难点在于小方法的命名。
假如有早上上学的一个大方法,
那么就应该在里面有起床,穿衣服,吃早点等小方法,
而起床这个方法又可以分为穿衣服,穿鞋,叠被子等方法,
而穿衣服又有穿内衣,穿外衣等步骤。
内联函数
一个太小的方法,里面只做了一个动作,就可以考虑去掉了,去掉会使代码更清晰。
感觉内联函数说的就是不要过度的提炼函数。
内联临时变量
就是说要尽量去掉只用一次的临时变量
注意:如何判断临时变量只被赋值了一次,就是在前面加flnal,能编译过就是只有一次
比如有代码:int age = user.getAge();return age>18;
那么这两句代码,就完全可以合成一句:return user.getAge()>18;
以查询取代临时变量
如果临时变量被用了多次的话,也尽量用方法去代替临时变量。
比如有代码:
int sum = a + b;
if(sum>10){
return sum2;
}
else{
return sum3;
}
这里的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。
将过程化设计转为对象设计
其实就是尽量的面向对象,将面向过程变成转成面向对象编程。
将领域和表述/显示分离
与界面展示相关的逻辑的类,和纯业务逻辑的类,要分离。
提炼继承体系
一个类在演变过程中,所承担的责任越来越多时,就应该考虑分离它了,可以采取抽出一部分当成子类,使整体变成继承的体系。