简介
“提取函数”(Extract Function)是一种常用的代码重构手法,它可以将一段具有特定功能的代码块从原函数中提取出来,形成一个新的独立函数。通过这种方式,可以提高代码的可读性、可维护性和复用性,使代码结构更加清晰,逻辑更加模块化。
针对的症状(代码坏味道)
- 过长函数:函数包含过多的代码逻辑,导致函数难以理解和维护。
- 重复代码:在多个地方出现相同或相似的代码片段,违反了代码复用原则。
- 逻辑复杂:代码块中的逻辑复杂,难以一眼看出其功能和意图。
提取函数(Extract Function)的详细步骤
- 识别需要提取的代码块
- 寻找重复代码:在代码中找到多次出现的相同或相似的代码片段。
- 识别复杂逻辑:找到逻辑复杂、难以理解的代码块,这些代码块通常实现了一个相对独立的功能。
- 评估代码块的独立性:确保提取的代码块具有一定的独立性,能够作为一个独立的功能单元存在。
- 创建新的函数
- 选择有意义的函数名:为新函数选择一个能够清晰描述其功能的名称,遵循命名规范。
- 提取代码块:将选定的代码块复制到新函数中。
- 确定参数:分析代码块中使用的外部变量,将这些变量作为参数传递给新函数。
- 确定返回值:如果代码块有计算结果,将该结果作为新函数的返回值。
- 替换原始代码
- 调用新函数:在原函数中,用调用新函数的语句替换提取的代码块。
- 清理未使用的变量:删除原函数中因提取代码块而不再使用的局部变量。
- 测试
- 编译代码:确保代码编译通过,没有任何语法错误。
- 运行测试:运行所有相关的单元测试,确保重构操作没有引入新的错误。
- 手动测试:如果有必要,进行手动测试以验证功能的正确性。
- 代码审查
- 同行评审:让同事或其他团队成员审查你的更改,确保代码质量和可维护性没有下降。
- 文档更新:如果项目有维护文档的习惯,记得更新相关文档,说明提取函数的影响。
示例
假设有一个函数 printUserInfo,其中包含一段计算用户年龄的复杂逻辑,我们希望对其进行“提取函数”的重构。
原始代码
import java.time.LocalDate;
import java.time.Period;
public class User {
private String name;
private LocalDate birthDate;
public User(String name, LocalDate birthDate) {
this.name = name;
this.birthDate = birthDate;
}
public void printUserInfo() {
LocalDate currentDate = LocalDate.now();
Period period = Period.between(birthDate, currentDate);
int age = period.getYears();
System.out.println("User Name: " + name);
System.out.println("User Age: " + age);
}
}
重构步骤
-
识别需要提取的代码块:计算用户年龄的逻辑
LocalDate currentDate = LocalDate.now(); Period period = Period.between(birthDate, currentDate); int age = period.getYears();是一个相对独立的功能,需要提取出来。 -
创建新的函数:
- 选择有意义的函数名:
calculateAge。 - 提取代码块并传递必要的参数:
private int calculateAge(LocalDate birthDate) { LocalDate currentDate = LocalDate.now(); Period period = Period.between(birthDate, currentDate); return period.getYears(); } - 选择有意义的函数名:
-
替换原始代码:
- 在
printUserInfo函数中,用调用新函数的语句替换提取的代码块:
import java.time.LocalDate; import java.time.Period; public class User { private String name; private LocalDate birthDate; public User(String name, LocalDate birthDate) { this.name = name; this.birthDate = birthDate; } public void printUserInfo() { int age = calculateAge(birthDate); System.out.println("User Name: " + name); System.out.println("User Age: " + age); } private int calculateAge(LocalDate birthDate) { LocalDate currentDate = LocalDate.now(); Period period = Period.between(birthDate, currentDate); return period.getYears(); } } - 在
-
测试:
- 编译代码:确保代码编译通过,没有任何语法错误。
- 运行测试:运行所有相关的单元测试,确保重构操作没有引入新的错误。
-
代码审查:
- 让同事审查代码,确保没有引入新的问题。
练习
基础练习题
-
过长函数的提取
-
给定以下 Java 代码,
processOrder方法过长且逻辑复杂。请将计算订单总价的部分提取成一个独立的方法。import java.util.ArrayList; import java.util.List; public class OrderProcessor { private List<Double> itemPrices; private double taxRate; public OrderProcessor(List<Double> itemPrices, double taxRate) { this.itemPrices = itemPrices; this.taxRate = taxRate; } public void processOrder() { double total = 0; for (double price : itemPrices) { total += price; } double tax = total * taxRate; double finalTotal = total + tax; System.out.println("Order Subtotal: " + total); System.out.println("Tax: " + tax); System.out.println("Order Total: " + finalTotal); } }
-
-
重复代码的提取
- 下面的 Java 代码中有两个方法
printEvenNumbers和printOddNumbers,都包含遍历数组的重复代码。请将遍历数组的代码提取成一个独立的方法。
public class NumberPrinter { public void printEvenNumbers(int[] numbers) { for (int num : numbers) { if (num % 2 == 0) { System.out.println(num); } } } public void printOddNumbers(int[] numbers) { for (int num : numbers) { if (num % 2 != 0) { System.out.println(num); } } } } - 下面的 Java 代码中有两个方法
进阶练习题
-
复杂逻辑块提取与参数传递
- 在这段 Java 代码中,
calculateFinalScore方法内的逻辑较为复杂。请将计算加权分数的逻辑提取成一个独立方法,并正确处理参数传递。
public class ScoreCalculator { private double[] scores; private double[] weights; public ScoreCalculator(double[] scores, double[] weights) { this.scores = scores; this.weights = weights; } public double calculateFinalScore() { double weightedTotal = 0; double weightSum = 0; for (int i = 0; i < scores.length; i++) { weightedTotal += scores[i] * weights[i]; weightSum += weights[i]; } return weightedTotal / weightSum; } } - 在这段 Java 代码中,
-
提取函数与返回值处理
- 给定以下 Java 代码,
processData方法包含复杂逻辑。请将数据筛选和转换的部分提取成一个独立方法,并确保返回值正确处理。
import java.util.ArrayList; import java.util.List; public class DataProcessor { public List<Integer> processData(int[] data) { List<Integer> result = new ArrayList<>(); for (int num : data) { if (num > 0) { result.add(num * 2); } } return result; } } - 给定以下 Java 代码,
综合拓展练习题
- 多模块提取与代码审查模拟
-
考虑一个简单的 Java 电商系统,有
Product类、Cart类和Order类。Cart类中的calculateCartTotal方法和Order类中的calculateOrderTotal方法都有重复且复杂的计算总价逻辑,同时Cart类中的applyDiscount方法逻辑也较为复杂。 -
请对这些方法进行 “提取函数” 重构,将重复和复杂逻辑提取成独立方法。
-
假设你完成了重构,请模拟一份简单的代码审查报告,指出重构后的优点和可能存在的潜在问题。
class Product { private double price; public Product(double price) { this.price = price; } public double getPrice() { return price; } } class Cart { private List<Product> products; private double discountRate; public Cart(List<Product> products, double discountRate) { this.products = products; this.discountRate = discountRate; } public double calculateCartTotal() { double total = 0; for (Product product : products) { total += product.getPrice(); } double discountedTotal = total * (1 - discountRate); return discountedTotal; } public void applyDiscount() { if (products.size() > 3) { discountRate += 0.1; } if (calculateCartTotal() > 100) { discountRate += 0.05; } } } class Order { private List<Product> products; private double shippingCost; public Order(List<Product> products, double shippingCost) { this.products = products; this.shippingCost = shippingCost; } public double calculateOrderTotal() { double total = 0; for (Product product : products) { total += product.getPrice(); } return total + shippingCost; } }
-