一、代码重构是什么
用重构手法去重构代码
二、为什么需要代码重构
1.方便维护。为了在修改代码时,添加的地方有个切入点,减少bug。
2.有利于理解设计思想和代码。
三、代码重构怎么做(养成习惯)
思想:
1.修改一点,测试一下,防止不知道错误在哪。
2.积小成多。每次发现就改一下。慢慢改好整个项目代码结构。
常见需要重构的地方
1.神秘命名-》(改名)
2.重复代码=》(1.重复代码提出为函数 2.对于相似不相同的,把相似的放在一起,移动重组3.对于同一超类不同子类中有重复代码,把这个函数移动到超类中)
3.过长函数=》分解函数。使用提炼函数,将需要注解说明的地方一般可以分成一个函数;可以使用以查询取代临时变量,消除临时或局部元素;引入参数对象和保持对象完整,将过长参数变成一个对象;以命令取代函数,把函数封装成一个类,像命令模式。
4.循环和条件。使用分解条件表达式,将条件和结果封装为函数,用三元表达式表示;switch如果是基于同一个条件,使用以多态取代条件表达式,用子类实现switch结果;对于循环,把循环提出作为函数;但是如果循环中有几件事就需要拆分循环。
5.过长的参数列表。1.使用查询取代参数 2.如果是从现有的数据结构中抽出数据,使用保持对象完整,将原来数据结构传入3.如果一些参数总是出现,用引入参数对象,把参数转为一个对象4.如果某个参数是用作flag且在调用的时候就决定了,使用移除标记参数,把flag多个结果封装成多个函数,调用方明确调用。5.如果多个函数有同样几个参数,也就是对这些参数数据进行操作,可以使用函数组合成类,把共同参数组合成类,类中加入操作数据的方法,客户端可以修改对象核心数据,计算后得到的结果与核心数据保持一致。
6.全局数据。1.使用封装变量,使得我们知道哪里修改过,即设get和set函数封装变量。再控制访问,搬移到类中,只允许模块内中代码使用,控制作用域。
7.可变数据。1.封装变量控制2.拆分变量。拆成不同变量3.提取函数。把逻辑和更新数据的代码分离。4.查询函数和修改函数分离。5.移除设值函数,把不可变的成员变量,通过构造函数传入,删掉set。6.以查询取代派生变量。解决当修改数据时忘记更新派生变量(用一个函数,即时计算出派生变量的值)7.用函数组合成类(参数变类,数据不变,只做计算,保证原始对象数据不变)和函数组合变换(深克隆一个对象,对深克隆对象操作,保证原始对象数据不变)8.将引用对象改为值对象。类A中包含类B b对象,b就是可变的,把他变不可变就是把使用移除设置函数。
8.发散式变化(不同上下文不同变化)。不同上下文1.如果有先后顺序,用拆分。2.如果来回调用。用搬移函数把处理逻辑分开。3.如果是混合处理逻辑,先提炼,再搬移。4.如果模块是以类来定义,用提炼类(1个类变变多个,且判断是否要暴露出来,搬移的是先搬移被其他函数调用,再搬调用其他函数)。
ps:上下文我的理解就是场景,不同上下文就是不同场景用到同一个函数
9.散弹式修改(一个变化需要修改多个类修改)。1.先用搬移函数和搬移搬到同一模块中,再看数据是否操作相似,是就用函数组合成类,而如果函数功能是转化或充实数据结构可以用函数组合成变换。如果是先组合数据,在计算数据,可以使用拆分阶段。总的就是:使用内联相关重构把分散逻辑聚集,再使用提炼重构拆分。
10.依恋情结(模块与模块数据交互>自己内部交互)。1.搬移函数搬过去2.提取函数,把其中方法提取出来3.使用访问者模式和策略模式
11.数据泥团(很多地方看到相同的数据【类中字段、参数】)。1.就是把相同参数变一个对象,根据这个类去找依恋情结。使用提炼类、引入参数对象、保持对象完整
12.基本类型偏执(类似钱、电话、单位不创建类,使用基本类型。导致毫米可能与英寸相加或大量if)。1.以对象取代基本类型。2.控制条件用子类取代类型码和以多态取代条件表达式
13.重复的switch。
14.循环语句。1.以管道取代循环
15.冗敖的元素(类、函数很简单,不需要)。1.内联函数、内联类2.折叠继承体系,把类中字段和函数移到超类,或相反
16.夸夸奇谈的通用性(确实用不到的钩子方法)。1.内联函数、内联类、折叠继承体系,改变函数声明、移除死代码
17.临时字段。1.提炼类,把所有和这字段有关代码提取2.引入特例(把特例给提出来作为类或字面量【js】,空值是特例中特例,有点类似空对象设计模式))。
18.过长的消息链(user请求对象a,得到对象b,再向对象b请求对象c…)。1.用隐藏委托关系(添加一个委托函数,直接调用)2.观察最终对象是用来干什么,提炼函数提炼到一个函数中,搬移函数搬移到消息链
19.中间人(过度委托)。移除中间人,与18相反。
20.内幕交易(模块之间私下交互数据)。1.搬移到公共地方管理2.隐藏委托关系,用委托类当中介管理3.以委托取代子类(子类函数搬到委托类【a:重写的,b:重写且调用超类的方法,需要委托类把该方法分成超类+扩展方法,扩展方法写在委托类中。c:只有子类有超类无的方法,在超类中添加分发逻辑的函数】,超类函数改变分发逻辑)或以委托取代超类
21.过大的类。1.提炼类、提炼超类、以子类取代类型码
22.异曲同工的类(可以替换的类,但接口不一致等)。1.改变函数声明、搬移函数、提炼超类
23.纯数据类(只有get和set)。1.把数据处理放入该类中。先字段管理,使用封装记录和移除设值函数;再找到字段被调用的地方,把数据处理用搬移函数和提炼函数变成一个函数放到该类中。2.如果是中转数据(只可读)是例外情况
24.被拒绝的遗赠(继承超类却拒绝继承超类实现可以忍,继承超类却拒绝支持超类接口就孰不可忍了)1.用委托取代子类或以委托取代超类划清界限2.传统做法,子类继承超类函数和数据,有些不想或不需要继承=》为子类新建兄弟类,用函数下移和字段下移把用不到的函数从超类下推到兄弟类。
25.注释(表示这里可能需要重构才需要注释)。1.注释解释代码做了什么,可以用提炼函数2.注释解释行为用来干什么,用改变函数声明3.注释说明系统规格,用引入断言4.注释一般是用来说将来打算或为什么做某事而写的这一段代码
重构所需要的环境
前提。有测试系统去避免总debug
重构手法
笔记草稿(有时间再整理,手法点这:重构手法):
1.查询取代临时变量。就是能够通过查到的数据,可以不通过传参传入,如length
2.使用内联变量。就是传的参数是用方法得到的,交给一个变量。
3.改变函数声明。如:可以减少一个传入参数
例子:1+2+3
A { -----------===》-----------A{ --------------------B{
局部变量 b ===》 ------------B(xx) ------------C(得到b)
B(xx,b)
} ------------==》----------} ---------------------}
重构后:虽然传入参数少了,但可能得到b要执行多次,所以性能可能会差点。看情况
4.内联变量。就是这个变量由一个函数得到,之后不会修改
5.将函数变量变成一个函数。(不常用)
6.改变函数声明。就是名字要明确。
7.拆分循环。一个循环变2个(最后变成两个变量)
8.移动语句。把变量声明挪动到循环位置
1+7+8=》把拆出的循环提炼为一个函数+4得到一个变量,
9.以管道取代循环。就是用前端类似lamb表达式的那种
10.以多态取代条件表达式。首先要把条件和逻辑用类包起来,用继承结构表示,
11.以子类取代类型码(条件)
12.提炼函数。写一个方法时有逻辑,有步骤,把某个逻辑的代码提取出来写作一个方法
结果:大量嵌套函数。
1。整理原函数结构。复杂代码分解小方法。如:所有代码写在一个function中变成多个嵌套函数
2.拆分阶段实现复用。把原函数按照某种层次进行步骤分解。如:先计算得到数据(得到一个中转数据),再把数据进行渲染,最后把这两部分变成2个文件,实现复用。
注:使用副本,是为了保持数据不可变性。(前端用的多,感觉)
可能会用搬移函数:即把函数方法放到子方法中。原来地方用中转数据。
四、重构困难
1.代码所有权。如:有些代码时第三方api或者模块不同的人只能改自己模块的代码
2.在分支上重构,可能会使得其他人开发调用有问题。办法(感觉不是很好):每天往master上merge一次
3.需要自测试的代码
五、自动化测试工具
java有idea和eclipse(有保证)。使用语法树替换函数换名字。而文本编辑器(灵活,没感觉出来)则是使用字符串替换,不是很能保证重构。新趋势是语言服务器(用软件生成语法树,给文本编辑器提供api)