要写出好代码,首先要知道什么是不好的代码,不好的代码有哪些坏味道,当代码出现腐化时怎么进行重构,常见的重构方法有哪些,代码重构的基本原则和最佳实践有哪些,本文将对这几个内容做一个总结。
一、代码的坏味道
代码坏味道定义:指代码表面的腐化现象,对需求易变性的估计不足、功能重复出现、片段式植入等代码腐化现象。
主要的坏味道分类:
冗余和重复
局部膨胀
耦合结构不良
代码坏味道的层次:
直观层面:一眼看过去就能识别的问题,比如:魔鬼数字、函数/类过长、圈复杂度高、函数/变量命令不规范等一般规范性问题,使用工具门禁都能扫描出来。
微观层面:需要仔细观察才能发现的问题,比如:类字段定义不合理、函数功能不单一、变量作用域过长,需要具体到代码行的具体问题
宏观系统层面:代码架构上的整体问题,如:类职责不单一、上帝类(违反单一原则)、分层不清楚、上下文混乱等,一般是需求本身设计、类定义、类机构设计问题,主要体现在业务与架构的发展问题。
不符合以下四个基本原则中任何一个,即可能存在代码坏味道:
通过所有测试。软件系统对外部需求能够被正确地完成,包括功能性需求和非功能性需求,都能满足用户验收的标准。
最小化重复代码,尽可能消除重复代码。确保软件能够高内聚,低耦合,达到良好正交性的过程
代码表达清晰,优雅。
代码设计的复杂度低,简单明了
总结代码坏味道四种类别:
分别包括膨胀剂,可有可无,滥用面向对象,难以修改与耦合,前面两个属于直观层面,第三个属于微观层面,第四个属于宏观层面。下面将详细讲述
(一)膨胀剂
1、过长的类
拥有短函数的对象活得比较好、比较长。间接层所能带来的全部利益——解释能力、共享能力、选择能力——都是由小型函数支持的。
每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立的函数中。
如何确定提炼哪一段代码?寻找注释是一个很好的技巧。它们通常能指出代码用途和实现手法之间的语义距离。如果代码前方有一行注释,就是提醒你:可以将这段代码替换成一个函数。
条件表达式和循环常常也是提炼的信号。
2、过大的类
如果想利用单个类做太多的事情,其内往往就会出现太多实力变量。
类内如果有太多代码,也是代码重复、混乱病最终走向死亡的源头。
3、过长参数列
太长的参数列难以理解,太多的参数会造成前后不一致、不容易使用,而且一旦你需要更多数据,就不得不修改它。如果将对象传递给函数,大多数修改都将没有必要。
4、数据泥团
很多地方看到相同的三四项数据一起出现。这些总是绑在一起出现的数据应该拥有属于他们自己的对象。
首先找到这些数据以字段形式出现的地方,将它们提炼到一个独立的对象中。这么做的直接好处是可以将很多参数列缩短简化函数调用。
5、基本类型偏执
对象的一个极大价值在于:它们模糊了横旦与基本数据和体积较大的类之间的界限
对象技术的新手通常不愿意在小任务上运用小对象——结合数值和比重的money类、有一个起始值和一个结束值组成的range类。将原本单独存在的数值替换成对象,从而走出传统的洞窟,进入炙手可热的对象世界。
(二)可有可无
1、重复代码
如果你在一个以上的地点看到相同的程序结构,那么可以肯定:设法将它们合二为一,程序会变得更好 。
同一个类中有相同的表达式:提炼出重复的代码,然后让两个地方都调用被提炼出来的那一段代码;
两个互为兄弟的子类内含有相同的表达式:提炼出相同代码,将它推入超类内;
两个毫不相干的类中出现:将重复的代码提炼到一个独立的类中。
2、冗余类
某个类原本对得起自己的身价,但重构使它身形缩水,不再做那么多工作,这个时候请让这个类庄严赴义吧。
3、夸夸其谈未来性
企图以各种各样的钩子和特殊情况来处理一些非必要的事情,这种怀味道就出现了。如果用到了那就值得去做,如果用不到那就不值得,只会挡你的路,所以把它挪开吧。
如果你的某个抽象类其实没有起到太大的作用,函数上的某些参数未被使用…可以移除它们了。
4、过多的注释
注释之所以存在是因为代码很糟糕 。注释的最高境界——代码即注释。
当你感觉需要撰写注释时,请先尝试重构,试着让所有的注释都变得多余。
5、纯稚的数据类
纯稚的数据类是指:它们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。
封装public字段;
恰当封装容器类字段;
移除不应修改的字段的设置函数;
提炼调用函数以隐藏取值/设值函数;
(三)滥用面向对象
1、异曲同工的类
两个函数做同一件事,却有着不同的签名。
2、不完美的类库
类库函数构造的不够好,又不能修改它们:
如果只想修改类的一两个函数,可以引入外加函数。如果想要添加一大堆额外行为,建立一个新类包含这些额外行为,让其成为子类。
3、switch惊悚现身
面向对象的一个最明显的特征是:少用switch语句一看到switch语句,就应该考虑以多态来替换它。
如果只是在单一函数中有些选择实例,且并不想改动它们,那么多态就有点杀鸡用牛刀了。
4、令人迷惑的暂时字段
某个实例变量仅为某种特定的情况而设。这样的代码让人不易理解。在变量未被使用的情况下猜测当初其设置目的,会让你发疯的。
5、被拒绝的遗赠
子类只运用了父类的一部分函数和数据。为子类建立一个兄弟类,将所有用不到的字段/函数下移至兄弟类,保证超类的纯粹;
(四)难以修改和耦合
1、发散式变化
如果某个类经常因为不同的原因在不同的方向上发生变化,那么此时也许将这个对象分成两个会更好,这么一来每个对象就可以只因为一种变化而需要修改。
2、散弹式修改
如果没遇到某种变化,你都必须在许多不同的类内做出许多小修改,你所面临的坏味道就是散弹式修改。如果需要修改的代码散布四处,你不但很难找到它们,也很容易忘记某个重要的修改。
把所有需要修改的代码放进同一个类中,如果眼下没有合适的类可以安置这些代码就创造一个。
3、平行集成体系
每当你为某个类增加一个子类,必须也为另一个类相应增加一个子类。
消除这种重复性的一般策略是:让一个继承体系的实例引用另一个继承体系的实例。
4、依恋情结
对象技术的要点在于:将数据和对数据的操作行为包装在一起.有一种经典的气味是:函数对某个类的兴趣高过对自己所处类的兴趣。某个函数为了计算某个值,从另一个对象那调用几乎半打的取值函数。
一个函数往往会用到几个类的功能,那么它该置于何处?我们的原则是:判断哪个类拥有最大被此函数使用的数据,然后就把这个函数和那些数据放在一起。
5、过度耦合消息链
如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另个一对象……..这就是消息链。采用这种方式,意味着客户代码将与查找过程中的导航结构紧密耦合。一旦对象间的关系发生任何变化,客户端就不得不做出相应的修改。
6、中间人
封装往往伴随着委托。你也许会看到某个类接口有一半的函数都委托给其他类,这样就是过度运用。
7、狎昵关系
有时会看到两个类过于亲密,话费太多的时间去探究彼此的private成分。过分狎昵的类必须拆散,帮它们划清界线,从而减少狎昵行径。
继承往往造成过度亲密,因为子类对超类的了解总是超过后者的主观愿望。如果你觉得该让孩子独立生活了,让他离开继承。
二、常见的6种重构方法
分别包括重新组织函数处理过程,重新组织数据,简化条件表达式,在对象之间搬移特性,简化函数调用,处理概括关系,前三个属于一个类内的重构优化,提升可读性和高聚低耦,后三个数据类结构层次的重构,提升可读性和高聚低耦。下面将详细讲述
(一)重新组织函数处理过程
1、提炼函数
动机:看到一个过长的函数或者一段需要注释才能让人理解用途的代码,将这段代码放一个独立的函数中;
做法:创造一个新函数,根据这个函数的意图来命名它;
只要新函数的名称能够以更好的方式昭示代码意图,你也应该提炼它。但如果想不到一个更有意义的名称就别动
将提炼的代码从原函数复制到新建的目标函数中;
将被提炼代码段中需要读取的局部变量,当作参数传递给目标函数;
在源函数中,将被提炼代码段替换为目标函数调用。
2、内联函数
一个函数的本体与名称同样清楚易懂。在函数调用点插入函数本体,然后移除该函数。
动机:一群组织不甚合理的函数。你可以将它们都内联到一个大函数中,再从中提炼出组织合理的小型函数。
2、找出这个函数的所有调用点;
3、将这个函数的所有调用点都替换成函数本体。
3、内联临时变量
有一个临时变量,只被一个简单的表达式赋值一次,而它妨碍了其他重构手法。
将所有对该变量的引用动作,替换为对它赋值的那个表达式自身
double basePrice = anOrder.basePrice();
return (base > 10000 );
替换为:
return (anOrder.basePrice > 1000);
4、以查询取代临时变量
你的程序以一个临时变量保存某一表达式的运算结果。将这个表达式提炼到一个独立的函数中。将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可被其他函数使用。
double basePrice = quantity * timePrice;
if(basePrice > 1000){
return basePrice * 09.5;
} else {
return basePrice * 0.98;
}
替换为:
if(basePrice() > 1000){
return basePrice * 09.5;
} else {
return basePrice * 0.98;
}
double basePrice(){
return quantity * timePrice;
}
临时变量只在所属的函数中可见,如果把临时变量替换为一个查询,那么同一个类中的所有函数都将可以获得这个份信息,这将带给你极大的帮助,使你能够为这个类编写更清晰的代码。
5、引入注释性变量
你有一个复杂的表达式。将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。
if ((platform.toUpperCase().indexOf("MAC") > -1) && (browser.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize >0){
//do smothing
}
替换为:
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize >0;
if(isMacOs && isIEBrowser && wasInitialized() && wasResized){
//do smothing
}
表达式有可能非常复杂难以理解。这种情况下,临时变量可以帮助你将表达式分解为比较容易管理的形式。
在条件逻辑中,你可以用这项重构将每个条件子句提炼出来,以一个良好命名的临时变量来解释对应条件子句的意义。另一种情况是:在较长的算法中,可以运用临时变量来解释每一步运算的意义。
6、分解临时变量
你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。针对每次赋值,创造一个独立、对应的临时变量。
double temp = 2 * (height + width);
System.out.println(temp);
temp = height * width;
System.out.println(temp);
替换为:
double perimeter = 2 * (height + width);
System.out.println(perimeter);
double area = height * width;
System.out.println(area);
如果临时变量被赋值超过一次,就意味着它们在函数中承担了一个以上的责任。
如果临时变量承担多个责任,它就应该被替换为多个临时变量。每个变量只承担一个责任,同一个临时变量承担两件不同的事情会令代码阅读者糊涂
7、移除对参数的赋值
代码对一个参数进行复制。以一个临时变量取代该参数的位置。
int discount (int inputVal, int quantity, int yearToData){
if(inputVal > 50) inputVal -= 2;
}
替换为:
int discount (int inputVal, int quantity, int yearToData){
int result = inputVal;
if(inputVal > 50) result -= 2;
}
如果代码的语义是按引用传递的,请在调用段检查调用后是否还使用了这个参数。
8、替换算法
想要把某个算法替换为另一个更清晰的算法。将函数本体替换成为另一个算法。
String foundPerson(String[] people){
for(int i = 0;i < people.length; i++){
if(people[i].equals("Don")){
return "Don";
}
if(people[i].equals("John")){
return "John";
}
if(people[i].equals("Kent")){
return "Kent";
}
}
return "";
}
替换为:
String foundPerson(String[] people){
List candidates = Arrays.asList(new String[]{"Don", "John", "Kent"});
for(int i = 0;i < people.length; i++){
if(candidates.contains(people[i])){
return prople[i];
}
}
return "";
}
(二)重新组织数据
1、自封装字段
直接访问一个字段。为这个字段建立取值/设值函数,并且只以这些函数来访问字段。
private int low, high;
boolean includes(int arg){
retrun arg >= low && arg <= high;
}
替换为:
private int low, high;
boolean includes(int arg){
retrun arg >= getLow() && arg <= getHigh();
}
int getLow(){
retrun low;
}
int getHigh(){
return high;
}
在“字段访问方式”这个问题上,存在两种截然不同的观点:
在该变量定义所在的类中,你可以自由的访问。
即使在这个类中你也应该只使用访问函数间接访问。
间接访问的好处是:子类可以通过复写一个函数而改变获取数据的途径;它支持更灵活的数据管理方式,例如延迟初始化。
2、以对象取代数据值
你有一个数据项,需要与其他数据和行为一起使用才有意义。将数据项变为对象。
一开始你肯能会用一个字符串来表示“电话号码”概念,但是随后你会发现,电话号码需要“格式化”、“区号”之类的行为。这时候就需要为带替换的数值新建一个类。
3、将值对象改为引用对象
你从一个类衍生出许多彼此相等的实例,希望将它们替换为同一个对象。将这个值对象变成引用对象。
4、将引用对象改为值对象
你有一个引用对象,很小且不可改变,而且不易管理。将它变成一个值对象。
5、以对象取代数组
你有一个数组,其中的元素各自代表不同的东西。以对象替换数组。对于数组中的每个元素,以一个字段来表示
6、复制“被监视数据”
你有一些领域数据置身GUI控件中,而领域函数需要访问这些数据。将该数据复制到一个领域对象中。建立一个Observer模式,用以同步领域对象和GUI对象内的重复数据。
7、将单向关联改为双向关联
两个类都需要使用对方特性,但其间只有一条单向连接。添加一个反向指针,并使修改函数能够同时更新两条连接。
8、将双向关联改为单向关联
两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性。去除不必要的关联。
9、以字面常量取代魔数
你有一个字面数值,带有特别含义。创造一个常量,根据其意义为它命名,并将上述的字面数值替换为常量。
10、封装字段
你的类中存在一个public字段。将它声明为private,并提供相应的访问函数。
11、封装集合
有个函数返回一个集合。让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。
(三)简化条件表达式
1、分解条件表达式
有一复杂的条件语句。从if、then、else三个段落中分别提炼出独立函数。
2、合并表达式
你有一系列条件测试,都得到相同结果。将这些测试合并为一个条件表达式,并将这个条件表达式提炼成一个独立函数。
3、合并重复的条件代码
在表达式的每个分支上都执行了相同的一段代码。将这段重复代码搬移到条件表达式之外。
4、移除控制标记
在一系列布尔表达式中,某个变量带有”控制标记”的作用。以break/return语句取代控制标记。
5、以多态取代条件表达式
有个条件表达式根据对象类型的不同而选择不同的行为。将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数
(四)在对象之间搬移特性
在对象设计过程中,决定把责任放在哪儿是即使不是最重要的事,也是最重要的事之一。
常常只使用搬移函数和搬移字段简单地移动对象行为,就可以解决这些问题。如果这两个重构手法都需要用到,我会首先使用搬移字段,再使用搬移方法。
如果一个类承担了太多责任而变得臃肿不堪,这种情况下会使用提炼类将一部分责任分离出去。如果一个类变得太不负责任,使用将类内联化将它融入到另一个类中。
1、搬移函数
你的程序中,有个函数与其所驻类之外的另个一类进行跟过的交流:调用后者或被后者调用。在该函数最长引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或者将旧函数完全移除。
如果一个类有太多行为,或如果一个类与另一个类有太多合作而高度耦合,就需要搬移函数。可以是系统中的类更简单
2、搬移字段
程序中,某个字段被其所驻类之外的另一个类更多的用到。在目标类新建一个字段,修改原字段的所有用户,令他们改用新字段
3、提炼类
某个类做了应该由两个类做的事。建立一个新类,将相关字段和函数从就类搬到新类。
4、将类内联化
某个类没有做太多的事情,不在承担足够责任,不再有的那单独存在的理由。将这个类的所有特性搬移到另一个类中,然后移除原类。
5、隐藏“委托关系”
客户通过一个委托类来调用另一个对象。在服务类上建立客户所需要的所有函数,用来隐藏委托关系。
封装意味每个对象都应该少了解系统的其他部分。一旦发生变化,需要了解这一变化的对象就会比较少。
如果某个客户先通过服务对象的字段得到另一个对象,然后调用后者的函数。那么客户就必须知晓这一层委托关系。万一委托关系变化,客户也要相应变化。
6、移除中间人
某个类做了过多的简单委托。让客户直接调用委托类。
每当客户要使用手委托类的新特性时,你就必须在服务端添加一个简单委托函数。随着受委托类的特性越来越多,这一过程会让你很痛苦。
7、引入外加函数
你需要为提供服务的类增加一个函数,但你无法修改这个类。在客户类中建立一个函数,并以第一参数形式传入一个服务类实例。
Date newStart = new Date(year, month, date + 1);
替换为:
Date newStart = nextDay(nowDate);
private static Date nextDay(Date arg){
retrun new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
}
如果可以修改源码,你可以自行添加一个新函数;如果不能,你就得在客户端编码,补足你要的那个函数
8、引入本地扩展
你需要为服务类提供一些额外函数,但你无法修改这个类。建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类或包装类。
(五)简化函数调用
1、函数改名
函数的名称未能揭示其用途。修改函数名称。
2、添加参数
某个函数需要从调用端得到更多信息。为此函数添加一个对象参数,让该对象带仅函数所需信息。
3、移除参数
函数本体不再需要某个参数。去除参数。
4、分离查询函数和修改函数
某个函数既返回对象状态值,又修改对象值。建立两个不同函数,其中一个负责查询,另一个负责修改。
5、令函数携带参数
若干函数做了类似的工作,但在函数本体中包含了不同的值。建立单一函数,以参数表达那些不同的值。
有这样两个函数:它们做着类似的工作,但因少数几个值致使行为略有不同。
在这种情况下,你可以将这些各自分离的函数统一起来,并通过参数来处理那些变化情况,用以简化问题。
6、以明确函数取代参数
你有一个函数,其中完全取决于参数值而采用不同行为。针对该参数的每一个可能值,建立一个独立函数。
如果某个参数有多种可能的值,而函数内又以条件表达式检查这些参数值,并根据不同参数值做出不同的行为,那么就应该使用本项重构。
7、保持对象完整
从某个对象中取出若干值,将它们作为某一次函数调用时的参数。改为传递整个对象。
8、以函数取代参数
对象调用某个函数,并将所得结果作为参数,传递给另一函数,而接受该参数的函数本身也能够调用前一个函数。让参数接受者去除该参数,直接调用前一个函数。
9、引入参数对象
某些参数总是很自然的同时出现。以一个对象取代这些参数。
10、移除设值函数
类中某个字段在对象创建时被设值,然后不再改变。去掉该字段的所有设值函数。
11、隐藏函数
某个函数,从来没有被其他任何类用到 。将函数修改为private。
12 、以工厂函数取代构造函数
希望在创建对象时不仅仅是做简单的建构动作 。将构造函数替换为工厂函数。
(六)处理概括关系
1、字段上移
两个子类拥有相同的字段。将该字段移至超类。
2 、函数上移
有些函数在各子类中产生完全相同的结果。将该函数移至超类。
3 、构造函数本体上移
各个子类中有一些构造函数本体几乎完全一致 。在超类中新建一个构造函数,并在子类构造函数中调用它。
4、函数下移
超类中的某个函数只与部分(而非全部)子类用到。将函数移到相关的子类中。
5、字段下移
超类中的某个字段只被部分(而非全部)子类用到。将字段移到需要它的子类中。
6、提炼子类
类中的某些特性只被某些(而非全部)实例用到。新建一个子类,将上述部分的特性移到子类中。
7、提炼超类
两个类有相似特性。为这两个类建立一个超类,将相同特性移至超类。
8、提炼接口
若干客户使用类接口中的同一子集,或两个类的接口有部分相同。将相同的子集提炼到一个独立接口中。
9、折叠继承体系
超类和子类之间无太大区别。将它们合为一体。
10、塑造模板函数
子类中某些函数以相同顺序执行类似操作,但各操作细节略有不同。将操作放进独立函数(保持签名相同),然后将它们移至超类。
11、以委托取代继承
某个子类只使用超类接口中的一部分或根本不需要继承而来的数据。子类新建字段保存超类,调整子类函数为委托超类,取消继承关系。
12、以继承取代委托
你在两个类中使用委托关系,并经常为整个接口编写许多极简单的委托函数。`让委托类继承受托类。
三、代码重构的基本原则和最佳实践
1. 代码重构的基本概念和意义
代码重构的意义在于改进代码质量和开发效率。下面是进行代码重构的几个主要原因:
提高可读性:重构可以简化复杂的代码逻辑,使代码更加易读、易懂,提高代码的可读性和可理解性。
提高可维护性:重构可以去除重复的代码、提取通用的功能模块,使代码更加模块化、可维护,降低维护成本。
提高扩展性:重构可以将代码设计为可扩展的结构,使其能够方便地添加新的功能和模块。
优化性能:重构可以对性能瓶颈进行优化,提升代码的执行效率和响应速度。
改进团队协作:重构可以规范代码的命名、注释和风格,使团队成员之间更容易理解代码,提高团队协作效率。
综上所述,代码重构是一个持续改进和优化代码质量的过程,对于提高软件开发效率和质量具有重要作用。在实际开发中,我们需要进行充分的准备工作,制定合理的重构计划,并遵守一些基本原则和最佳实践,以确保代码重构的顺利进行。
2. 代码重构的基本原则
代码重构是一项复杂而细致的工作,为了确保重构的顺利进行和最终效果,我们需要遵循一些基本的原则。下面将介绍代码重构的基本原则,帮助开发者更好地进行代码重构工作。
1)保持原有功能不变
在进行代码重构的过程中,最基本的原则就是要保持原有功能不变。无论进行怎样的修改和优化,都不能改变程序原有的功能和逻辑。这就要求在重构前后,通过充分的测试和验证,确保程序的功能和输出结果保持一致。只有在经过充分的测试后,才能将重构后的代码投入使用,否则可能会引入新的bug和问题。
2) 逐步实施和逐步验证
代码重构并不是一蹴而就的工作,而是需要逐步进行和逐步验证的过程。在进行代码重构时,建议采用渐进式的方式,将大的重构任务拆分为小的步骤,逐步实施和验证。每一次小的改动都需要进行测试和验证,确保不会引入新的问题。只有在一步步确保正确后,才能继续下一步的重构。
3) 使用可靠的工具和技术支持
在进行代码重构时,一些可靠的工具和技术支持能够极大地提高效率和保证质量。比如,代码静态分析工具能够帮助发现代码中的潜在问题和不规范之处;版本控制系统能够帮助跟踪每一次的改动和回滚到任意版本;自动化测试工具能够确保重构后的代码依然符合预期。因此,在进行代码重构时,要善于利用各种工具和技术手段,提高重构的效率和质量。
4) 编写清晰的文档和注释
在进行代码重构时,一定要编写清晰的文档和注释。重构后的代码,可能会涉及到结构的调整以及算法的优化,对于这些修改,一定要在代码中详细注明修改的原因和思路。同时,也要及时更新相关的文档,确保团队中的其他成员能够理解和使用新的代码。良好的文档和注释,对于代码维护和团队协作具有重要意义。
通过遵循这些基本原则,可以更好地进行代码重构,提高代码的质量和可维护性。在实际工作中,要根据具体的情况灵活运用这些原则,做出最适合项目的决策。
3、代码重构的 14 条最佳实践
代码重构是一个能够显著提升代码质量的重要步骤,它带来了我们之前强调的诸多好处。但在进行重构时也需谨慎,特别是在处理庞大的代码库或不熟悉的代码库时,以避免无意中改变软件的功能或产生未预见的问题。
编辑搜图为了避免任何潜在问题,您可以参考以下重构 Java 代码的技巧和最佳实践:
保持功能不变:Java 代码重构的首要目标是提高代码质量。然而,程序的外部行为,如它如何响应输入和输出以及其他用户交互应该保持不变。
充分理解代码:在开始重构某个代码片段之前,请确保你充分理解你即将重构的代码。这包括其功能、交互和依赖关系。这种理解将指导你的决策,并帮助你避免做出可能影响代码功能的更改。
将 Java 重构过程分解为小步骤:重构大型软件,尤其是当你对它还不够了解时,可能会令人感到不知所措。然而,通过将重构过程分解为更小、易于管理的步骤,你可以使工作负担更轻,减少错误的风险,并容易持续验证你的更改。
使用版本控制创建备份:由于重构涉及对代码库进行更改,有可能事情不会按计划进行。在一个单独的分支上备份你的工作软件是一个好主意,以避免在找不出是哪些更改破坏了你的软件时浪费大量时间和资源。像 Git 这样的版本控制系统允许你同时管理不同的软件版本。
频繁测试你的更改:在重构代码时你最不想做的就是不小心破坏程序的功能或引入影响软件的错误。在进行任何重大的代码更改之前,尤其是重构,建立一套测试集是提供安全网的好方法。这些测试验证你的代码行为是否符合预期,以及现有的功能是否保持完整。
利用重构工具:有了像 Eclipse 和 IntelliJ 这样的现代 IDE,Java 代码重构不必是一个紧张的过程。例如,IntelliJ IDEA 包括一套强大的重构功能。一些功能包括安全删除,提取方法/参数/字段/变量,内联重构,复制/移动等。这些功能使你的工作更轻松,减少了你在重构过程中引入错误的机会。
深入理解代码变更:在重构过程中,深入了解你所做的代码变更是非常重要的,它可以帮助你快速识别和解决可能出现的问题。
充分利用单元测试:作为开发者,我们需要确保在重构代码时不会破坏现有的应用程序或引入新的 bug。一个完善的单元测试套件可以帮助你检测回归问题,确保功能的完整性,同时也有利于团队合作和长期的代码维护。
持续跟踪性能变化并反馈:Java 代码重构不仅仅是为了改善代码结构,它还涉及到性能优化。通过持续监控性能指标,你可以确保你的重构工作正在取得实质性的进展。
进行同行代码审查:重构完成后,建议邀请另一名开发者来审查你的更改,他们可以从一个新的角度来发现可能被你忽视的问题,并提供有价值的反馈。
记录重构变更:当你与其他开发者一起工作时,记录你的重构变更非常重要,因为这样可以提高透明度和协作效率,同时也有助于新员工更快地了解项目。
进行回归测试:完成重构后,需要通过回归测试来确保所有现有的功能都得到了保留,并且新引入的逻辑没有与现有代码产生冲突。
保持团队同步:在多人开发团队中工作时,及时通报你的重构变更非常必要,这可以避免冲突并帮助团队更好地适应新的变更。
在必要时执行回滚:如果重构过程中遇到无法解决的问题,不要犹豫,立即回滚到一个稳定的状态,以避免浪费更多的时间和资源。