最近看了一本关于代码重构的书–《重构-改善既有代码的设计》,闲暇时来读,发现收益颇丰,下面就把自己的几点体会写下与大家分享
一:印象最深刻的就是书中提到的各种代码的“坏味道”,就是说一旦你的代码中出现了这几种“坏味道”,就是你该代码重构的时候了,举几个容易被大家遗忘的事例来说明
(1)不要出现发散式变化:意思是说对一个class,最好对应一种变化,而不是对应很多种变化。比如出现一个class对应三种变化的情况,那么最好把这个class分成三个,每个class对应一种变化,这样,每一个class就可以只因为一种变化而需要修改。是不是感觉代码变得容易修改了?
(2)不要出现散弹式修改:这种情况与上一种恰恰相反,就是说一种变化最好不要修改多个类,因为如果一种变化要修改的代码散布四处,你不但难找到它们,也很容易忘记某个重要的修改。
(3)前面的(1)和(2)要说明的是,类和变化的关系最好是一对一的关系,这样写出来的代码才会清晰,而且可维护性很高。
(4)不要出现纯粹的数据类:纯粹的数据类是指,它们拥有一些值域,以及这些值域的get和set方法,除此之外什么都没有。消除这种class的方法就是:首先,找出这些取值/设值函数被其他classes运用的函数,如果这些函数与这个数据类的联系比它本身的宿主类还紧密,就尝试着把这些函数搬移到这个纯粹的数据类中来。如果无法搬移,就从这个函数中提取一个可被搬移的函数,再把这个函数搬移到纯粹的数据类中来。
(5)不要出现冗余类:项目中经常出现这样的情况,一个class刚开始可能很有用,但是随着时间的迁移,逐渐的被人遗弃了。在这个时候,就应该勇敢的舍弃它,删掉它(因为如果还有别的函数要引用它,编译器会报错的)。
(6)有的时候也不能出现过多的注释:过多的注释往往意味着你的这段代码写得很糟糕,需要通过长长的注释才能让别人理解,因此,在这种情况下,通常需要把这块代码的业务
逻辑提取成几个函数,然后替这几个函数取几个好名字,最后只要调用这几个函数就行了。这样的话,就算你不写注释,别人来读你这段代码的时候,也会感觉在读注释一样轻松 。
二:另外在这本书中还提到了一些很有意思的重构方法
(1)用对象来代替方法:大家常常会碰到这样的函数,这个函数很长,很明显需要被拆分成好几个函数了,但是由于被此函数太多的局部变量的拖累,使得拆分变得很困难。当然你可以通过传参数来解决,但是过多的参数的传来传去必然会导致代码的清晰度受到很大的影响。那么在这种情况下,就应该把这个函数转化成一个单独的类,那么,原函数中的局部变量也就变成这个类中的值域,想想看,这样的话,对原有函数的拆分是不是就变得简单了。
(2)用表达式来代替临时变量:有的时候会出现这种情况,你的程序中的一个表达式被赋给了一个临时变量,然后接下来的程序中调用的都是这个临时变量而不是这个表达式(有的人会说这样性能会提高一些)。 是的,这样性能是会提高一些,但是想一下,如果你的程序中有很多的这样的临时变量,那你的这段程序是不是很难被拆分成几个更小的函数,也就是很难被重构成结构更优化的代码。而情况往往就是如果你的代码的结构比较优化,清晰度比较高,那么你就会发现更有效的性能优化的方案。所以就会出现两种情况,要么你用很多的临时变量来提高一点点性能,从而与更有效的性能优化的方案失之交臂;要么你通过一点点性能的损失,来换取更有效的性能优化的方案。你选哪个?
(3)把临时变量拆分成多个临时变量 :在很多种情况下,你的程序中的临时变量被赋值很多次,但是它既不是循环变量(如for(int i=0;i<100;i++)语句中的i),也不是一个累加临时变量(例如字符串的拼接)那么,我们就应该针对每一次赋值,创造一个独立的,对应的临时变量,使得每一个变量只承担一个责任。因为如果一个临时变量承担多个责任,就会使代码阅读者感到糊涂。
(4)用一个临时变量来取代参数被赋值:如果要使一个函数内的程序变得清晰,一个很好的方法就是不要改变你传进这个函数的参数,因为这样会使阅读者搞不清楚这个参数所承担的责任。因此,如果程序中看到你的参数被赋值了,那么,我想最好的办法就是用一个临时变量来取代参数来进行赋值动作。然后,以赋值动作为界,将后面程序对参数的引用动作,全部改为对临时变量的引用动作
(5)以对象取代数组 :有很多时候,你会有一个数组,这个数组中的每一个元素代表不同的东西。那么,在这种情况下,我们就应该以对象取代数组,对于数组中的每一个元素,以一个值域
表示之 ,举个例子:
String[] p=new String[2];
p[0]=”man”;
p[1]=”23″;
p[2]=”mocui”;
用对象代替,就变成
People p=new People();
p.setSex(”man”);
p.setAge(”23″);
p.setName(”mocui”);
数组应该用来容纳一组相似的对象,如果用来容纳了数种不同对象,你会发现一些问题,比如你很难去记住p[0]代表的是什么,而p[1]代表的又是什么,但是用对象来封装这些数据就不同了,你通过p.getSex(),p.getName()能够很容易知道我们得到的数据代表的是性别和名字。而且如果使用对象,你还可以把与这个对象相关的行为提取成一个个方法,放到这个对象中。这样代码的清晰度将会进一步提升。
当然,在相似的情况下,用对象也可以来取代List等集合。
总结:代码重构的首要目标是使得代码的可读性,可维护性和可扩展性进一步提高。因此,你会发现很多的重构方法是不惜牺牲一点点的性能来取得代码的清晰度的,原因有几点:
其一:一段可读性很高的代码能够节省程序员理解代码的大部分时间,提高程序开发的效率,与之相比,一点点性能的损失又算的了什么呢?
其二:如果一段代码重构的好,你往往会发现接下来会有更好的性能优化方法,而如果你没有通过损失一点点的性能来重构你的代码,你往往会与这个方法失之交臂。
一:印象最深刻的就是书中提到的各种代码的“坏味道”,就是说一旦你的代码中出现了这几种“坏味道”,就是你该代码重构的时候了,举几个容易被大家遗忘的事例来说明
(1)不要出现发散式变化:意思是说对一个class,最好对应一种变化,而不是对应很多种变化。比如出现一个class对应三种变化的情况,那么最好把这个class分成三个,每个class对应一种变化,这样,每一个class就可以只因为一种变化而需要修改。是不是感觉代码变得容易修改了?
(2)不要出现散弹式修改:这种情况与上一种恰恰相反,就是说一种变化最好不要修改多个类,因为如果一种变化要修改的代码散布四处,你不但难找到它们,也很容易忘记某个重要的修改。
(3)前面的(1)和(2)要说明的是,类和变化的关系最好是一对一的关系,这样写出来的代码才会清晰,而且可维护性很高。
(4)不要出现纯粹的数据类:纯粹的数据类是指,它们拥有一些值域,以及这些值域的get和set方法,除此之外什么都没有。消除这种class的方法就是:首先,找出这些取值/设值函数被其他classes运用的函数,如果这些函数与这个数据类的联系比它本身的宿主类还紧密,就尝试着把这些函数搬移到这个纯粹的数据类中来。如果无法搬移,就从这个函数中提取一个可被搬移的函数,再把这个函数搬移到纯粹的数据类中来。
(5)不要出现冗余类:项目中经常出现这样的情况,一个class刚开始可能很有用,但是随着时间的迁移,逐渐的被人遗弃了。在这个时候,就应该勇敢的舍弃它,删掉它(因为如果还有别的函数要引用它,编译器会报错的)。
(6)有的时候也不能出现过多的注释:过多的注释往往意味着你的这段代码写得很糟糕,需要通过长长的注释才能让别人理解,因此,在这种情况下,通常需要把这块代码的业务
逻辑提取成几个函数,然后替这几个函数取几个好名字,最后只要调用这几个函数就行了。这样的话,就算你不写注释,别人来读你这段代码的时候,也会感觉在读注释一样轻松 。
二:另外在这本书中还提到了一些很有意思的重构方法
(1)用对象来代替方法:大家常常会碰到这样的函数,这个函数很长,很明显需要被拆分成好几个函数了,但是由于被此函数太多的局部变量的拖累,使得拆分变得很困难。当然你可以通过传参数来解决,但是过多的参数的传来传去必然会导致代码的清晰度受到很大的影响。那么在这种情况下,就应该把这个函数转化成一个单独的类,那么,原函数中的局部变量也就变成这个类中的值域,想想看,这样的话,对原有函数的拆分是不是就变得简单了。
(2)用表达式来代替临时变量:有的时候会出现这种情况,你的程序中的一个表达式被赋给了一个临时变量,然后接下来的程序中调用的都是这个临时变量而不是这个表达式(有的人会说这样性能会提高一些)。 是的,这样性能是会提高一些,但是想一下,如果你的程序中有很多的这样的临时变量,那你的这段程序是不是很难被拆分成几个更小的函数,也就是很难被重构成结构更优化的代码。而情况往往就是如果你的代码的结构比较优化,清晰度比较高,那么你就会发现更有效的性能优化的方案。所以就会出现两种情况,要么你用很多的临时变量来提高一点点性能,从而与更有效的性能优化的方案失之交臂;要么你通过一点点性能的损失,来换取更有效的性能优化的方案。你选哪个?
(3)把临时变量拆分成多个临时变量 :在很多种情况下,你的程序中的临时变量被赋值很多次,但是它既不是循环变量(如for(int i=0;i<100;i++)语句中的i),也不是一个累加临时变量(例如字符串的拼接)那么,我们就应该针对每一次赋值,创造一个独立的,对应的临时变量,使得每一个变量只承担一个责任。因为如果一个临时变量承担多个责任,就会使代码阅读者感到糊涂。
(4)用一个临时变量来取代参数被赋值:如果要使一个函数内的程序变得清晰,一个很好的方法就是不要改变你传进这个函数的参数,因为这样会使阅读者搞不清楚这个参数所承担的责任。因此,如果程序中看到你的参数被赋值了,那么,我想最好的办法就是用一个临时变量来取代参数来进行赋值动作。然后,以赋值动作为界,将后面程序对参数的引用动作,全部改为对临时变量的引用动作
(5)以对象取代数组 :有很多时候,你会有一个数组,这个数组中的每一个元素代表不同的东西。那么,在这种情况下,我们就应该以对象取代数组,对于数组中的每一个元素,以一个值域
表示之 ,举个例子:
String[] p=new String[2];
p[0]=”man”;
p[1]=”23″;
p[2]=”mocui”;
用对象代替,就变成
People p=new People();
p.setSex(”man”);
p.setAge(”23″);
p.setName(”mocui”);
数组应该用来容纳一组相似的对象,如果用来容纳了数种不同对象,你会发现一些问题,比如你很难去记住p[0]代表的是什么,而p[1]代表的又是什么,但是用对象来封装这些数据就不同了,你通过p.getSex(),p.getName()能够很容易知道我们得到的数据代表的是性别和名字。而且如果使用对象,你还可以把与这个对象相关的行为提取成一个个方法,放到这个对象中。这样代码的清晰度将会进一步提升。
当然,在相似的情况下,用对象也可以来取代List等集合。
总结:代码重构的首要目标是使得代码的可读性,可维护性和可扩展性进一步提高。因此,你会发现很多的重构方法是不惜牺牲一点点的性能来取得代码的清晰度的,原因有几点:
其一:一段可读性很高的代码能够节省程序员理解代码的大部分时间,提高程序开发的效率,与之相比,一点点性能的损失又算的了什么呢?
其二:如果一段代码重构的好,你往往会发现接下来会有更好的性能优化方法,而如果你没有通过损失一点点的性能来重构你的代码,你往往会与这个方法失之交臂。