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