extract method(提炼函数)
你有一段代码可以被组织在一起并提炼出来
// 提炼前
void printOwning(double amount){
printBananer();
// print details
Sysout.out.println("name:" + _name);
Sysout.out.println("amount:" + amount);
}
// 提炼后
void printOwning(double amount){
printBananer();
printDetails(amount)
}
void printDetails(double amount){
Sysout.out.println("name:" + _name);
Sysout.out.println("amount:" + amount);
}
动机
-
函数越小可读性越高
-
函数越小复用的机会就越大
可读性高的前提是能给函数取一个好名字
根据名字就可以直接明白函数是干啥的,而不需要跳转进去
需要一定的词汇量并且会归纳
做法
- 取一个好名字
我们提炼的目的是增加代码的可读性,如同看文章一样,见到函数的名就知道它是做什么的,而不用再去关心它是怎么做的,提高可读性。
所以需要以函数做什么
命名,而不是怎么做
举例:
查询订单的失效时间后,与当前时间判断,早于当前时间则订单失效,晚于当前时间则订单有效。
这里我们做什么?判断订单是否有效
所以提炼的方法名应为:isOrderEffective(),而不是compareWithNowTime()
前者是做什么,后者是怎么做
- 提炼代码从源函数复制到新建的目标函数中
- 检查变量,有没有用到源函数的局部变量和参数;
这里的目的是要看我们提炼的函数产生的影响,会不会受源函数的影响。
- 检查变量,有没有只在目标函数中使用的变量,如果有的话,在目标函数中声明为临时变量
- 检查被提炼代码段,看看有没有局部变量的值被它改变。
如果有一个临时变量被修改了,看看能否将被提炼代码段处理为一个查询,并将结果赋值给相关变量。
如果被修改的变量有点多了,就要先拆解变量,再来单独提炼。
- 将被提炼代码段中需要读取的局部变量,当做参数传给目标函数
- 处理完所有局部变量,编译
- 在源函数中,将被提炼代码段替换为对目标函数的调用,删除未使用的临时变量声明
- 编译、测试
范例
// 提炼前
void printOwning(){
Enumeration e = _orders.elements();
double outstanding = 0.0;
// print banner
Sysout.out.println("**********************");
Sysout.out.println("****Customer Ownes****");
Sysout.out.println("**********************");
// calculate outstanding
while(e.hasMoreElements()){
Order each = (Order)e.nextElement();
outstanding += each.getAmount();
}
// print details
Sysout.out.println("name:" + _name);
Sysout.out.println("amount:" + outstanding);
}
// 1. 无局部变量,提炼函数 printBanner()
void printOwning(){
Enumeration e = _orders.elements();
double outstanding = 0.0;
printBanner();
// calculate outstanding
while(e.hasMoreElements()){
Order each = (Order)e.nextElement();
outstanding += each.getAmount();
}
// print details
Sysout.out.println("name:" + _name);
Sysout.out.println("amount:" + outstanding);
}
void printBanner(){
Sysout.out.println("**********************");
Sysout.out.println("****Customer Ownes****");
Sysout.out.println("**********************");
}
// 2. 使用到了源函数的局部变量且为只读,将源函数具备变量传入目标函数
void printOwning(){
Enumeration e = _orders.elements();
double outstanding = 0.0;
printBanner();
// calculate outstanding
while(e.hasMoreElements()){
Order each = (Order)e.nextElement();
outstanding += each.getAmount();
}
printDetails(outstanding);
}
void printDetails(double outstanding){
Sysout.out.println("name:" + _name);
Sysout.out.println("amount:" + outstanding);
}
// 3. 对局部变量再赋值:使用源函数局部变量,并且进行了操作
void printOwning(){
printBanner();
double outstanding=getOutStanding();
printDetails(outstanding);
}
double getOutStanding(){
Enumeration e = _orders.elements();
double outstanding = 0.0;
while(e.hasMoreElements()){
Order each = (Order)e.nextElement();
outstanding += each.getAmount();
}
return outstanding;
}
// 4. 回传值改名
double getOutStanding(){
Enumeration e = _orders.elements();
double result = 0.0;
while(e.hasMoreElements()){
Order each = (Order)e.nextElement();
result += each.getAmount();
}
return result;
}
上面的情况中 getOutStanding
是没有入参的,因为源函数只是对 outstanding
做了初始化,而没有做其他操作,如果源函数中对 outstanding
做了其他的操作,就需要将 outstanding
作为入参传入。
// 提炼前
void printOwning(double previousAmount){
Enumeration e = _orders.elements();
double outstanding = previousAmount * 1.2;
printBanner();
// calculate outstanding
while(e.hasMoreElements()){
Order each = (Order)e.nextElement();
outstanding += each.getAmount();
}
printDetails(outstanding);
}
// 1. 提炼之后,将操作后的 局部变量 作为入参传入目标函数
void printOwning(double previousAmount){
double outstanding = previousAmount * 1.2;
printBanner();
outstanding = getOutStanding(outstanding)
printDetails(outstanding);
}
double getOutStanding(double initialValue){
double result = initialValue;
Enumeration e = _orders.elements();
while(e.hasMoreElements()){
Order each = (Order)e.nextElement();
result += each.getAmount();
}
return result;
}
// 2. 去除临时变量,优化outstanding初始化过程
void printOwning(double previousAmount){
printBanner();
double outstanding = getOutStanding(previousAmount * 1.2)
printDetails(outstanding);
}