技术是为了更好得解决变动的需求,要做到这一点,必须写出优雅的代码。这需要重构,
- 这是一个开发人员从初级到中级的必经之路
- 重构是目的,设计模式是到达的方式,这需要一直的训练
重构是什么
- 什么是重构,调整软件内部结构,提高理解性,降低修改成本;但不改变软件可观察行为(功能)
- 何时重构,不用专门拨时间进行重构,应该是随时随地进行的。当想为程序添加特性,但代码结构使你不是很方便达成目的,可考虑先重构。应该好程序应满足下面条件
- 容易阅读
- 所有逻辑都只在唯一地址指定
- 新的改动不会危及现有行为
- 尽可能简单表达条件逻辑
- 重构难题,数据库、修改接口
- 重构和性能,重构代码有时会为了改善结构而牺牲性能,尽管如此你也不必要求代码处处高性能。一视同仁优化所有代码,90%工作是白费的,你优化的代码很少被运行。所以,编写良好代码结构,不对性能投以特别的关注,直至进入性能优化阶段(后期阶段)。
代码的坏味道
- 重复代码,提取方法,把方法提取到父类
- 过长函数,提取方法
- 难点:怎样解决参数过多的问题
- 过大的类,提取类或者子类
- 过长参数列
- 由于需求变动,导致代码块多处逻辑都修改(不符合逻辑在唯一处)。
- 依赖情结,类中某方法,太过依赖其他类数据,move it
- 数据泥团,提取出类
- 令人迷惑的暂时字段?
- 过多的注释,好代码,注释是多余的,写注释前请先尝试重构。
提炼函数(Extract Method)
- extract Method
- 难点:源函数参数,局部变量(个数越多,越难extract)
- 临时变量
- Inline Method,不需要extract 函数,提取了之后感觉这一中间层很别扭。
- 函数体来代替 函数名
- 查询函数代替临时变量:临时变量局限于某个函数,函数可以在对象整个生命周期中使用。
内联临时变量,引入解释临时变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23# 内联临时变量
let xx = aaa.b;
if(xx >21){
dosomething;
}
<!-- 之后 -->
if(aaa.b >21){
dosomething;
}
# 引入解释临时变量
if(aaa.bb.c.indexof('xx') > -1 && ddd.ee.f > -1){
dosomething;
}
<!-- 之后 -->
const a = aaa.bb.c.indexof('xx') > -1;
const b = ddd.ee.f > -1;
if(a && b){
dosomething;
}分解临时变量,当一个临时变量被赋值了两次,可以引入两个独立 临时变量
- i = i + xx 形式不要拆解
- 拒绝对函数参数赋值
- 引发很多,函数引用传参的问题,java 更多是提倡按值传递。
- js 动态语言,很多都是对象,注意 深拷贝 的问题
- 类来代替函数(replace method with Object)
- 当一个函数局部变量很多,逻辑复杂,extract 困难。就把函数升级为类
- 替换算法,简单逻辑替换复杂逻辑
引入解释临时变量,和抽取函数选择哪一种。
对象之间搬移特性
- 搬移函数、搬移字段,如果一类里面包含太多行为,或者里面的函数和其他类耦合高,被调用次数比驻类(宿主类)还多。可以考虑搬移函数,搬移之前,往往先进行搬移字段,如果字段被驻类很多函数调用,先进行自我封装(self-Encapsulation)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60class A ...
private B b;
private double xx;
double xxx1(double x1, int x2){
return xx * x1 * x2;
}
double xxx2(double x1, int x2){
return xx * x1 * x2;
}
# 将 xx 般至B里面
<!-- A 进行自我封装 -->
class A ...
private B b;
private double xx;
double xxx1(double x1, int x2){
return xx * x1 * x2;
}
double xxx2(double x1, int x2){
return xx * x1 * x2;
}
private void setxx(double arg){
xx = arg;
}
private double getxx(){
return xx
}
<!-- xx 移到B -->
class B ...
private double xx
void setxx(double arg){
xx = arg;
}
double getxx(){
return xx;
}
<!-- 这样,就算A中有很多对xx 的引用,就只要修改-->
private void setxx(double arg){
// xx = arg;
b.setxx(arg);
}
private double getxx(){
// return xx
return b.getxx();
}
其实自我封装,也是多加了一层。一层函数调用
- 提炼类,类内联化(Inline Class),一个类太大,抽象得不合理,应该提炼。内联是指一个类没有承担足够多的责任,不应该单独存在,将这个类内联到其他类中。
隐藏委托关系,移除中间人,如下,B把责任全部都委托给了C,如果A 完全不知道C 的存在,则称为隐藏委托关系。有时候抽象的目的就在于此,减少耦合。但如果B 这个中间人(Middle Man)越来越大时,干脆就直接移出中间人B,直接调用C
1
# A调用 B,B再调用 C获得结果后,给 A返回
引入本地函数,或者本地扩展,当某语言的标注库函数提供的功能无法满足开发需求,可以引入本地函数,相当于java 函数重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 比如微信小程序request 封装一下
function request(option){
// dosometing();
wx.request(option);
}
# 调用自己写的request 函数
requset({
url: url,
method: 'GET',
success: function(res) {
// xxx
},
fail: function(err) {
// xxx
}
})
重新组织数据
- 自封装字段, 在类里面操作字段用函数,不直接访问。是否封装,一直以来存在争议。建议先不封装,如后面不满足需求时再封装,就像将字段搬移到其他类前,先进行字段自我封装一样。
- 对象取代数值、对象取代数组
- 值对象改为引用对象、引用改为值
- 两个类之间的双向关联,就像双向绑定一样,在js 动态语言实现更方便,无需提前说明字段,直接添加。用过
- layaAir 游戏里面,物理引擎body 和现实画面物体的坐标进行帧循环同步
- 配置常量代替魔法数字
- 封装字段,类里面的字段应该设置为private,并提供set/get 方法给外部访问。否则,字段被外部操作,类却不知道(因为直接访问字段)。这样做把数据和行为分开了。
- 数据类取代记录,把数据归类,抽到类中,该类没有只有数据,没有行为
- 类型码的取代,如 switch/case
- 类取代类型码
- 子类取代类型码
- state/strategy(设计模式) 取代类型码
重新组织数据的方法,其中4、5、6、7 比较常用,5、7对数据归类,抽取配置是必须要求的。对类型码的处理,等涉及了再熟悉。
简化条件表达式
分解、合并条件表达式,比如if 成立条件判断涉及多种情况,但是为一个目的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 分解条件表达式
// 是否不是夏天
if(date.before(SUMMER_START) || date.after(SUMMER_END)){
......
}
// 可以抽取条件判断为一个函数,签名为 notSummer(date)
# 合并条件表达式
double disabilityAmount(){
if(xx1) return 0;
if(xx2) return 0;
if(xx3) return 0;
}
// 可以合并上面判断逻辑合并重复的条件片段,比如if/else 内都含一段逻辑,可以提取至外面
- break/return 代替控制标记,结构化编程原则指明,每一个子程序只能有一个入口一个出口(单一入口原则),选择加入控制标记,降低可读性。因此编程语言提供,break/continue/return
- 卫语句取代嵌套条件表达式
- 多态取代条件表达式
- 引入null 对象
简化函数调用
- 修改函数名,可能一开始没法取一个适当的名字
- 添加、移除参数
- 将查询、修改函数分离,每个函数只做一件事
- 保持对象完整,将要传递的多个参数替换成一个对象,也是引入参数对象
以工厂函数代替构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39class Employee{
private int _type;
static final int ENGINEER = 0;
static final int SALESMAN = 0;
static final int MANAGER = 0;
Employee(int type){
_type = type;
}
}
# 改为 工厂函数
static Employee create(int type){
return new Employee(type);
}
# 更近一步
// 应该吧 engine/salesman/mannager 抽出成子类
static Employee create(int type){
switch(type){
case ENGINEER:
return new Engineer();
case SALESMAN:
return new Salesman();
case MANAGER:
return new Mannager();
default:
throw new IllegalArgumentException("xx");
}
}
# 简化switch
static Employee create(String name){
try{
return (Employee)Class.forName(name).newInstance();
}catch(Exception e){
throw new IllegalArgumentException("xxx");
}
}封装向下转型,java 5 模板机制,模板类?
欢迎大家给我留言,提建议,指出错误,一起讨论学习技术的感受!