您现在的位置是:首页 >技术教程 >Java代码重构学习笔记-重新组织函数网站首页技术教程
Java代码重构学习笔记-重新组织函数
Extract Method (提炼函数)
它的目的是将一个较长的方法拆分成较小的方法,以提高代码的可读性、可维护性和复用性。
举个例子,假设有一个名为 calculateSalary 的方法,它的作用是计算员工的薪水,并且包含了很多逻辑和业务规则。如果这个方法太长了,不利于后续的维护和扩展,我们就可以使用 Extract Method 进行重构。
首先,我们可以选择其中一个功能比较独立的部分,例如计算基本工资,将其封装成一个独立的方法,比如:
public double calculateBaseSalary(Employee employee) {
// 计算基本工资
double baseSalary = ...
return baseSalary;
}
接着,我们可以继续将其他类似的功能封装成独立的方法,比如计算加班费、计算奖金等等,最终重构后的代码可能会像下面这样:
public double calculateSalary(Employee employee) {
// 计算基本工资
double baseSalary = calculateBaseSalary(employee);
// 计算加班费
double overtimePay = calculateOvertimePay(employee);
// 计算奖金
double bonus = calculateBonus(employee);
// 计算总薪水
double totalSalary = baseSalary + overtimePay + bonus;
return totalSalary;
}
private double calculateBaseSalary(Employee employee) {
// 计算基本工资
double baseSalary = ...
return baseSalary;
}
private double calculateOvertimePay(Employee employee) {
// 计算加班费
double overtimePay = ...
return overtimePay;
}
private double calculateBonus(Employee employee) {
// 计算奖金
double bonus = ...
return bonus;
}
通过这样的重构,我们将原本过于庞大的方法分解成了若干个较小的、功能单一的方法,提高了代码的可读性、可维护性和复用性,更加符合面向对象的设计原则。
Inline Method (内联函数)
它的目的是消除一个只被调用了一次的简单函数,将函数体的内容直接替换到调用处,从而简化代码结构。
举个例子,假设有一个名为 getDiscount 的方法,它的作用是返回客户的折扣率。如果这个方法只被一个地方调用,并且非常简单,比如:
public double getDiscount(Customer customer) {
return customer.getMembershipStatus() == MembershipStatus.GOLD ? 0.2 : 0.1;
}
这个方法只是根据客户的会员等级返回不同的折扣率,由于它比较简短,只被调用了一次,因此可以考虑使用 Inline Method 进行重构,将其直接替换为调用它的代码,使得代码更加简洁清晰,比如:
double discount = customer.getMembershipStatus() == MembershipStatus.GOLD ? 0.2 : 0.1;
通过 Inline Method 重构后的代码会更加紧凑,避免了不必要的方法调用开销,同时也更加清晰易懂。
需要注意的是,在进行 Inline Method 重构时,需要考虑到原始代码中涉及到的变量和参数是否在调用处能够访问到,并且需要保证替换后的代码逻辑和原始代码的逻辑等价。
Inline Temp (内联临时变量)
它的目的是消除只被赋值一次的临时变量,直接将其替换为变量的赋值表达式,从而简化代码结构。
举个例子,假设有一个名为 getPrice 的方法,它的作用是返回产品的实际售价。如果该产品的折扣率大于0.5,则返回原价减去半价后的价格;否则返回原价减去折扣价后的价格。在方法中定义了一个临时变量 discountPrice 用于存储折扣价,比如:
public double getPrice(Product product) {
double discount = product.getDiscount();
double discountPrice = product.getPrice() * discount;
double finalPrice;
if (discount > 0.5) {
finalPrice = product.getPrice() - (product.getPrice() / 2);
} else {
finalPrice = product.getPrice() - discountPrice;
}
return finalPrice;
}
在这段代码中,discountPrice 只被赋值了一次,并且在后面的逻辑中也没有再使用到,因此可以考虑使用 Inline Temp 进行重构,将 discountPrice 直接替换为其赋值表达式,使得代码更加简洁清晰,比如:
public double getPrice(Product product) {
double discount = product.getDiscount();
double finalPrice;
if (discount > 0.5) {
finalPrice = product.getPrice() - (product.getPrice() / 2);
} else {
finalPrice = product.getPrice() - (product.getPrice() * discount);
}
return finalPrice;
}
通过 Inline Temp 重构后的代码会更加紧凑,避免了不必要的临时变量开销,同时也更加清晰易懂。
需要注意的是,在进行 Inline Temp 重构时,需要确保替换后的表达式计算结果和原始代码的逻辑等价,并且不会对代码的可读性和可维护性造成负面影响。
Replace Temp with Query (以查询取代临时变量)
它的目的是消除只被赋值一次的临时变量,直接使用一个相关的查询函数替换该变量的所有引用,从而简化代码结构。
举个例子,假设有一个名为 getFinalPrice 的方法,它的作用是返回产品的最终售价。如果该产品的折扣率大于0.5,则返回原价减去半价后的价格;否则返回原价减去折扣价后的价格。在方法中定义了一个临时变量 discount 用于存储折扣率,比如:
public double getFinalPrice(Product product) {
double discount = product.getDiscount();
double discountPrice = product.getPrice() * discount;
double finalPrice;
if (discount > 0.5) {
finalPrice = product.getPrice() - (product.getPrice() / 2);
} else {
finalPrice = product.getPrice() - discountPrice;
}
return finalPrice;
}
在这段代码中,discount 只被赋值了一次,并且在后面的逻辑中也只被使用了一次,因此可以考虑使用 Replace Temp with Query 进行重构,将 discount 替换为一个相关的查询函数 getDiscount(),使得代码更加简洁清晰,比如:
public double getFinalPrice(Product product) {
double finalPrice;
if (product.getDiscount() > 0.5) {
finalPrice = product.getPrice() - (product.getPrice() / 2);
} else {
finalPrice = product.getPrice() - (product.getPrice() * product.getDiscount());
}
return finalPrice;
}
通过 Replace Temp with Query 重构后的代码会更加紧凑,避免了不必要的临时变量开销,同时也更加清晰易懂。
需要注意的是,在进行 Replace Temp with Query 重构时,需要确保替换后的查询函数逻辑和原始代码完全等价,并且不会对代码的可读性和可维护性造成负面影响。
Introduce Explaining Variable (引入解释性变量)
它的目的是通过引入一个有意义的变量来解释复杂、难以理解的表达式,从而使代码更加易读懂。
举个例子,假设有一个名为 calculatePayment 的方法,它的作用是计算某位雇员的应付工资。该雇员的基本工资为 monthlySalary,加班费按 overtimeHours 计算,每小时加班费为 hourlyRate(当加班时间超过40小时时,加班费翻倍)。该方法的实现如下:
public double calculatePayment(double monthlySalary, int overtimeHours, double hourlyRate) {
double payment = monthlySalary + (overtimeHours * hourlyRate);
if (overtimeHours > 40) {
payment += (overtimeHours - 40) * hourlyRate;
}
return payment;
}
在这段代码中,计算加班费的表达式比较复杂,难以一眼看出其含义。因此可以考虑使用 Introduce Explaining Variable 进行重构,引入一个有意义的变量,比如 overtimePay,来解释加班费的计算过程,使得代码更加易读懂,比如:
public double calculatePayment(double monthlySalary, int overtimeHours, double hourlyRate) {
double overtimePay = Math.max(overtimeHours - 40, 0) * hourlyRate * 2
+ Math.min(overtimeHours, 40) * hourlyRate;
double payment = monthlySalary + overtimePay;
return payment;
}
通过 Introduce Explaining Variable 重构后的代码更容易理解和维护,变量 overtimePay 将复杂的计算过程进行了封装,让代码更加清晰易读。
需要注意的是,在进行 Introduce Explaining Variable 重构时,需要确保引入的变量有足够的语义,能够准确表达其所代表的含义,并且不会对代码的执行效率造成影响。
Split Temporary Variable (分解临时变量)
它的目的是将被赋值多次的临时变量拆分成多个不同的变量,从而使代码更加清晰易懂。
举个例子,假设有一个名为 calculateArea 的方法,用于计算一个矩形的面积。该矩形的长和宽分别为 length 和 width,面积通过 length * width 计算得出。在方法中定义了一个临时变量 area 用于存储计算后的面积,比如:
public double calculateArea(double length, double width) {
double area = length * width;
// do something else with area
area = area * 2;
// do something else with area
area = Math.sqrt(area);
return area;
}
在这段代码中,area 被赋值了多次,并且每次重新赋值都覆盖了之前的值,因此可以考虑使用 Split Temporary Variable 进行重构,将其拆分为多个不同的变量,使得代码更加清晰易读,比如:
public double calculateArea(double length, double width) {
double baseArea = length * width;
// do something else with baseArea
double doubledArea = baseArea * 2;
// do something else with doubledArea
double squareRootedArea = Math.sqrt(doubledArea);
return squareRootedArea;
}
通过 Split Temporary Variable 重构后的代码更加易读,每个变量只表示一个明确的含义,减少了变量被覆盖和误用的风险。
需要注意的是,在进行 Split Temporary Variable 重构时,需要确保拆分后的变量有明确的语义,能够准确表达其所代表的含义,并且不会对代码的执行效率造成影响。
Remove Assignments to Parameters (移除对参数的赋值)
它的目的是避免在方法中对参数进行重新赋值,从而使代码更加清晰易懂。
举个例子,假设有一个名为 calculateSalary 的方法,用于计算某位雇员的应付工资。该雇员的基本工资为 baseSalary,加班费按 overtimeHours 计算,每小时加班费为 hourlyRate(当加班时间超过40小时时,加班费翻倍)。在方法内部对参数进行了重新赋值,并计算出应付工资,比如:
public double calculateSalary(double baseSalary, int overtimeHours, double hourlyRate) {
baseSalary = baseSalary * 1.2;
if (overtimeHours > 40) {
hourlyRate = hourlyRate * 2;
}
double salary = baseSalary + (overtimeHours * hourlyRate);
return salary;
}
在这段代码中,对参数 baseSalary 和 hourlyRate 进行了重新赋值,这会使得代码难以理解和维护。因此可以考虑使用 Remove Assignments to Parameters 进行重构,避免对参数进行重新赋值,比如:
public double calculateSalary(double baseSalary, int overtimeHours, double hourlyRate) {
double adjustedSalary = baseSalary * 1.2;
if (overtimeHours > 40) {
hourlyRate = hourlyRate * 2;
}
double salary = adjustedSalary + (overtimeHours * hourlyRate);
return salary;
}
通过 Remove Assignments to Parameters 重构后的代码更加易读,不再修改原始参数,而是将计算得到的值赋给新的变量,避免了代码的混淆和误用。
需要注意的是,在进行 Remove Assignments to Parameters 重构时,需要确保不会影响方法的业务逻辑和参数传递的正确性。并且需要确保新定义的变量有明确的语义,能够准确表达代表的含义。
Replace Method with Method Object (以函数对象取代函数)
它的目的是将一个长而复杂的方法提取成一个单独的类,从而使代码更加清晰易懂。
举个例子,假设有一个名为 calculateOrderPrice 的方法,用于计算某个订单的总价。该订单包含多种商品,每种商品的单价为 price,数量为 quantity,可能存在折扣优惠或者运费等额外费用。在方法中实现了复杂的价格计算逻辑,比如:
public double calculateOrderPrice(List<Item> items, double discount, double shippingFee) {
double totalPrice = 0;
for (Item item : items) {
double itemPrice = item.getPrice() * item.getQuantity();
totalPrice += itemPrice;
}
if (discount > 0) {
totalPrice *= (1 - discount);
}
totalPrice += shippingFee;
return totalPrice;
}
在这段代码中,calculateOrderPrice 方法的逻辑较为复杂,对参数进行了多次计算和赋值,容易产生代码冗余和难以维护。因此可以考虑使用 Replace Method with Method Object 进行重构,将该方法提取到一个单独的类中,比如:
public class OrderPriceCalculator {
private List<Item> items;
private double discount;
private double shippingFee;
public OrderPriceCalculator(List<Item> items, double discount, double shippingFee) {
this.items = items;
this.discount = discount;
this.shippingFee = shippingFee;
}
public double calculateOrderPrice() {
double totalPrice = 0;
for (Item item : items) {
double itemPrice = item.getPrice() * item.getQuantity();
totalPrice += itemPrice;
}
if (discount > 0) {
totalPrice *= (1 - discount);
}
totalPrice += shippingFee;
return totalPrice;
}
}
通过 Replace Method with Method Object 重构后,价格计算被封装为了一个单独的类,可以更加灵活地处理复杂的价格计算需求,提高代码的可维护性和可读性。
需要注意的是,在进行 Replace Method with Method Object 重构时,需要确保新提取出来的类具有明确的职责,并且类的命名和方法的命名都能够准确表达其所代表的含义。同时需要确保原始方法的行为不会因为提取到新的类中而发生改变。
Substitute Algorithm(替换算法)
它的目的是将一个算法替换成另一个更加简单明了的算法,从而提高代码的可读性和可维护性。
举个例子,假设有一个名为 findEmployee 方法,用于在员工列表中查找某个雇员。该方法采用顺序查找的算法实现,比如:
public Employee findEmployee(String employeeId, List<Employee> employees) {
for (Employee employee : employees) {
if (employee.getId().equals(employeeId)) {
return employee;
}
}
return null;
}
在这段代码中,findEmployee 方法使用了顺序查找的算法来查找目标雇员。然而,顺序查找的时间复杂度较高,当员工列表很大时,查找效率将会明显降低,因此可以考虑使用 Substitute Algorithm 进行重构,将顺序查找的算法替换成更高效的二分查找算法,比如:
public Employee findEmployee(String employeeId, List<Employee> employees) {
int left = 0;
int right = employees.size() - 1;
while (left <= right) {
int mid = (left + right) / 2;
Employee employee = employees.get(mid);
int cmp = employee.getId().compareTo(employeeId);
if (cmp < 0) {
left = mid + 1;
} else if (cmp > 0) {
right = mid - 1;
} else {
return employee;
}
}
return null;
}
通过 Substitute Algorithm 重构后,查找算法被替换成了二分查找算法,因此在大数据量的情况下,查找效率将得到明显提升,同时代码的可读性和可维护性也得到了改善。
需要注意的是,在进行 Substitute Algorithm 重构时,需要确保新算法的逻辑正确性,并且要注意对原有代码进行充分测试,以确保新代码的正确性和稳定性。同时,还需要考虑使用新算法带来的额外开销和资源消耗,确保其不会影响程序的整体性能和稳定性。