代码重构的目的
代码重构是指在不改变代码功能的前提下,通过修改代码的内部结构和外部表现形式,来提高代码的可读性、可维护性、性能和可扩展性。
通常包括以下几个方面:
- 改进代码的结构,使代码更加清晰简洁。
- 消除代码中的重复部分,减少代码冗余。
- 提高代码的可读性,使代码更加易于理解和维护。
- 提高代码的性能,减少代码的执行时间和内存占用。
- 改善代码的可扩展性,使代码更容易被扩展和修改。
如何保证重构过程中不会引入新的bug?
既然想要重构,就意味着要修改代码,修改代码就可能引入新的bug。
所以,重构只能保证设计的改进,而不能保证程序没有bug。
目前发现bug的有效手段就是充分有效的测试,而保证充分有效的测试的关键是单元测试的高覆盖率。
什么时候选择重构?什么时候选择重写?
这个问题只能个人判断,很难给出一个有共通性的同类情况。
不过,一些需要重写的代码肯定有迹象:
- 例如:某个项目由于编程语言太老或者平台环境太老导致推进速度较慢,不适用于当前情况。
- 此时,这种情况下,重构代码的作用微乎其微,就需要选择重写了。
代码重构的方法
方法提取:
一个方法不宜超过50行,超过50行的代码,就充斥着 代码坏味道。
方法提取是指将一段代码抽象出来形成一个方法。
这样做的好处是可以减少代码的重复,提高代码的可读性和可维护性。
提取变量:
提取变量是指将一段表达式抽象出来形成一个变量。
这样做的好处是可以减少代码的重复,提高代码的可读性和可维护性。
案例说明,重构前的代码:
public double calculateTotalAmount(List<InvoiceItem> items) {
double totalAmount = 0;
for (InvoiceItem item : items) {
totalAmount += item.getPrice() * item.getQuantity();
}
if (totalAmount > 100) {
totalAmount *= 0.9;
}
return totalAmount;
}
在上述代码中,计算每个项目的金额是通过
item.getPrice() * item.getQuantity()
表达式来实现的。
重构后的代码:
将这个表达式抽象成一个变量 itemAmount,使代码更变得加易于理解和维护。
public double calculateTotalAmount(List<InvoiceItem> items) {
double totalAmount = 0;
for (InvoiceItem item : items) {
double itemAmount = item.getPrice() * item.getQuantity();
totalAmount += itemAmount;
}
if (totalAmount > 100) {
totalAmount *= 0.9;
}
return totalAmount;
}
重构条件语句:
核心思想:通过简化、合并或提取条件语句,使代码更加清晰和易于理解。
重构前的代码:
public boolean canCreateAccount(Customer customer) {
boolean canCreate = true;
if (customer.getAge() < 18) {
canCreate = false;
}
if (customer.getAccountNumber() != null && customer.getAccountNumber().length() != 0) {
canCreate = false;
}
if (customer.getCreditScore() < 500) {
canCreate = false;
}
return canCreate;
}
代码中,判断客户是否有资格创建账户的过程是通过多个条件语句实现的,如果还有其他情况,只能通过添加if/else的方法实现。
这不符合一段优秀代码的定义,根据重构条件语句的方式对其重构。
重构后的代码:
将多个条件语句合并成一个方法 isCustomerEligible(),使代码更加清晰易读。
对于这种多分支的逻辑语句,有各种不同的重构方法,在日常开发工作中,对于多条件判断的重构,最常用的是设计模式中的策略模式,大部分判断逻辑,都可用策略模式进行重构。
public boolean canCreateAccount(Customer customer) {
boolean canCreate = true;
if (!isCustomerEligible(customer)) {
canCreate = false;
}
return canCreate;
}
private boolean isCustomerEligible(Customer customer) {
if (customer.getAge() < 18) {
return false;
}
if (customer.getAccountNumber() != null && customer.getAccountNumber().length() != 0) {
return false;
}
if (customer.getCreditScore() < 500) {
return false;
}
return true;
}
提取抽象类:
指将多个类中的公共方法抽象出来形成一个抽象类,使得这些类可以继承这个抽象类来继承公共方法。
这样做的好处是可以减少重复代码,提高代码的复用性和可维护性。
重构前的代码:
public class SavingsAccount {
private double balance;
private double interestRate;
public SavingsAccount(double balance, double interestRate) {
this.balance = balance;
this.interestRate = interestRate;
}
public double getBalance() {
return balance;
}
public double getInterestRate() {
return interestRate;
}
public double calculateInterest() {
return balance * interestRate;
}
}
public class CheckingAccount {
private double balance;
private double transactionFee;
public CheckingAccount(double balance, double transactionFee) {
this.balance = balance;
this.transactionFee = transactionFee;
}
public double getBalance() {
return balance;
}
public double getTransactionFee() {
return transactionFee;
}
public double calculateTransactionFee() {
return transactionFee;
}
}
代码中,SavingsAccount 和 CheckingAccount 类有很多相同的方法,如 getBalance() 方法。
通过提取抽象类的方式对代码进行重构。
重构后的代码:
public abstract class Account {
protected double balance;
public Account(double balance) {
this.balance = balance;
}
public double getBalance() {
return balance;
}
public abstract double calculateInterest();
}
public class SavingsAccount extends Account {
private double interestRate;
public SavingsAccount(double balance, double interestRate) {
super(balance);
this.interestRate = interestRate;
}
public double getInterestRate() {
return interestRate;
}
public double calculateInterest() {
return balance * interestRate;
}
}
public class CheckingAccount extends Account {
private double transactionFee;
public CheckingAccount(double balance, double transactionFee) {
super(balance);
this.transactionFee = transactionFee;
}
public double getTransactionFee() {
return transactionFee;
}
public double calculateTransactionFee() {
return transactionFee;
}
}