盘点 10 个代码重构的小技巧
本次我们抛开 JAVA 虚拟机源码这些相对底层的东西,LZ 来与各位探讨一下几个代码重构的小技巧,这些内容部分来自于书籍当中,部分来自于 LZ 维护项目当中的一些实践经验。如果猿友们曾经用过这种手法,也不妨参与到文章的留言当中,将你的小心得、小体会共享与他人,也可以拿来冲击 LZ 自己定义的排行榜,LZ 不甚欢迎。
重构的手法有很多种,相对而言,一篇文章的涵盖量自然是无法提到所有,LZ 这里也只能提出一些平时会经常使用的一些手法,像一些比较高端的手法,各位有兴趣的可以去找一些专门的书籍涉猎。
另外还有一点,由于 LZ 是做 JAVA 开发的,因此部分重构小技巧可能与 JAVA 语言,或者说与面向对象的语言息息相关,不过大多数技巧,无论是面向过程的语言,还是面向对象的语言,都是可以相互通用的。
废话不多说,我们来看看实用重构技巧的排行榜吧。
No.1:重复代码的提炼
重复代码是重构收效最大的手法之一,进行这项重构的原因不需要多说。它有很多很明显的好处,比如总代码量大大减少,维护方便,代码条理更加清晰易读。
它的重点就在于寻找代码当中完成某项子功能的重复代码,找到以后请毫不犹豫将它移动到合适的方法当中,并存放在合适的类当中。
小实例
class
BadExample
{
public
void
someMethod1
()
{
//code
System.
out
.println(
"
重复代码"
);
/*
重复代码块 */
//code
}
public
void
someMethod2
()
{
//code
System.
out
.println(
"
重复代码"
);
/*
重复代码块 */
//code
}
}
/* ---------------------
分割线---------------------- */
class
GoodExample
{
public
void
someMethod1
()
{
//code
someMethod3();
//code
}
public
void
someMethod2
()
{
//code
someMethod3();
//code
}
public
void
someMethod3
()
{
System.
out
.println(
"
重复代码"
);
/*
重复代码块 */
}
}
这其中有一点是值得注意的,由于我们在分割一个大方法时,大部分都是针对其中的一些子功能分割,因此我们需要给每一个子功能起一个恰到好处的方法名,这很重要。可以说,能否给方法起一个好名字,有时候能体现出一个程序猿的大致水准。
小实例
class
BadExample
{
public
void
someMethod
()
{
//function[1]
//function[2]
//function[3]
}
}
/* ---------------------
分割线---------------------- */
class
GoodExample
{
public
void
someMethod
()
{
function1();
function2();
function3();
}
private
void
function1
()
{
//function[1]
}
private
void
function2
()
{
//function[2]
}
private
void
function3
()
{
//function[3]
}
}
有一个专业名词叫卫语句,可以治疗这种恐怖的嵌套条件语句。它的核心思想是,将不满足某些条件的情况放在方法前面,并及时跳出方法,以免对后面的判断造成影响。经过这项手术的代码看起来会非常的清晰,下面 LZ 就给各位举一个经典的例子,各位可以自行评判一下这两种方式,哪个让你看起来更清晰一点。
小实例
class
BadExample
{
public
void
someMethod
(Object A,Object B)
{
if
(A !=
null
) {
if
(B !=
null
) {
//code[1]
}
else
{
//code[3]
}
}
else
{
//code[2]
}
}
}
/* ---------------------
分割线---------------------- */
class
GoodExample
{
public
void
someMethod
(Object A,Object B)
{
if
(A ==
null
) {
//code[2]
return
;
}
if
(B ==
null
) {
//code[3]
return
;
}
//code[1]
}
}
下面请尚且不明觉厉的猿友看下面这个典型的小例子。
小实例
class
BadExample
{
public
void
someMethod
(Object A,Object B)
{
if
(A !=
null
) {
if
(B !=
null
) {
//code
}
}
}
}
/* ---------------------
分割线---------------------- */
class
GoodExample
{
public
void
someMethod
(Object A,Object B)
{
if
(A !=
null
&& B !=
null
) {
//code
}
}
}
小实例
class
BadExample
{
private
int
i;
public
int
someMethod
()
{
int
temp = getVariable();
return
temp *
100
;
}
public
int
getVariable
()
{
return
i;
}
}
/* ---------------------
分割线---------------------- */
class
GoodExample
{
private
int
i;
public
int
someMethod
()
{
return
getVariable() *
100
;
}
public
int
getVariable
()
{
return
i;
}
}
小实例
class
BadExample
{
public
void
someMethod
(int i,int j,int k,int l,int m,int n)
{
//code
}
}
/* ---------------------
分割线---------------------- */
class
GoodExample
{
public
void
someMethod
(Data data)
{
//code
}
}
class
Data
{
private
int
i;
private
int
j;
private
int
k;
private
int
l;
private
int
m;
private
int
n;
//getter&&setter
}
顺便提一句,与此类情况类似并且最常见的,就是 Action 基类中,对于 INPUT、LIST、SUCCESS 等这些常量的提取。
小实例
class
BadExample
{
public
void
someMethod1
()
{
send(
"
您的操作已成功!"
);
}
public
void
someMethod2
()
{
send(
"
您的操作已成功!"
);
}
public
void
someMethod3
()
{
send(
"
您的操作已成功!"
);
}
private
void
send
(String message)
{
//code
}
}
/* ---------------------
分割线---------------------- */
class
GoodExample
{
protected
static
final
String SUCCESS_MESSAGE =
"
您的操作已成功!"
;
public
void
someMethod1
()
{
send(SUCCESS_MESSAGE);
}
public
void
someMethod2
()
{
send(SUCCESS_MESSAGE);
}
public
void
someMethod3
()
{
send(SUCCESS_MESSAGE);
}
private
void
send
(String message)
{
//code
}
}
小实例
class
BadExample
{
public
int
someMethod
(Data data)
{
int
i = data.getI();
int
j = data.getJ();
int
k = data.getK();
return
i * j * k;
}
public
static
class
Data
{
private
int
i;
private
int
j;
private
int
k;
public
Data
(int i, int j, int k)
{
super
();
this
.i = i;
this
.j = j;
this
.k = k;
}
public
int
getI
()
{
return
i;
}
public
int
getJ
()
{
return
j;
}
public
int
getK
()
{
return
k;
}
}
}
/* ---------------------
分割线---------------------- */
class
GoodExample
{
public
int
someMethod
(Data data)
{
return
data.getResult();
}
public
static
class
Data
{
private
int
i;
private
int
j;
private
int
k;
public
Data
(int i, int j, int k)
{
super
();
this
.i = i;
this
.j = j;
this
.k = k;
}
public
int
getI
()
{
return
i;
}
public
int
getJ
()
{
return
j;
}
public
int
getK
()
{
return
k;
}
public
int
getResult
()
{
return
i * j * k;
}
}
}
大部分时候,我们拆分一个类的关注点应该主要集中在类的属性上面。拆分出来的两批属性应该在逻辑上是可以分离的,并且在代码当中,这两批属性的使用也都分别集中于某一些方法当中。如果实在有一些属性同时存在于拆分后的两批方法内部,那么可以通过参数传递的方式解决这种依赖。
类的拆分是一个相对较大的工程,毕竟一个大类往往在程序中已经被很多类所使用着,因此这项重构的难度相当之大,一定要谨慎,并做好足够的测试
No.10:提取继承体系中重复的属性与方法到父类
往往这一类重构都不会是小工程,因此这一项重构与第九种类似,都需要足够的谨慎与测试。而且需要在你足够确认,这些提取到父类中的属性或方法,应该是子类的共性的时候,才可以使用这项技巧。
结束语
限于最后两种与实际情况的联系太过紧密,因此 LZ 无法给出简单的实例,不过后面两种毕竟不是常用的重构手法,因此也算是可以接受了。不过不常用不代表不重要,各位猿友还是要知道这一点的。另外 LZ 还要说的是,上面的实例只是手法的一种简单展示,实际应用当中,代码的结构可能是千奇百怪,但却万变不离其宗。因此只要抓住每种手法的核心,就不难从这些乱军丛中安然穿过。
好了,本次的小分享到此结束,各位猿友如果觉得有所收获,可以推荐一下鼓励下 LZ,顺便也让更多的人看到。这样的话,或许我们每一个接手的项目代码,都不至于十分的糟糕了,也算是给像 LZ 这样的项目维护者一条生路吧。