重构手法——数据重组类 | 数据关系重构 | 将语句移动到函数中

51 阅读6分钟

简介

“将语句移动到函数中”(Move Statements into Function)是一种重构手法,通过将一段代码移动到已有的函数中,可以减少代码重复并提高代码的可读性。这种方法特别适用于那些在多个地方重复出现的代码块。以下是进行“将语句移动到函数中”重构的详细步骤:

针对的症状(代码坏味道)

  • 重复的代码(Duplicated Code)
  • 过长的方法(Long Method)
  • 代码块内的逻辑过于复杂(Complex Logic in a Block)

将语句移动到函数中(Move Statements into Function)的详细步骤

  1. 识别需要移动的代码块
    • 寻找重复代码:在代码中找到重复出现的代码块。
    • 识别复杂逻辑:找到逻辑复杂且难以理解的代码块。
    • 评估代码块的独立性:确保移动的代码块具有一定的独立性和完整性,可以作为一个独立的方法存在。
  2. 选择目标函数
    • 选择合适的目标函数:选择一个已经存在的函数,该函数的功能与需要移动的代码块相关。
    • 确保目标函数的参数和返回值能够满足需求:确保目标函数能够接收必要的参数,并返回必要的值。
  3. 移动代码块
    • 将代码块移动到目标函数中:将选定的代码块复制到目标函数中。
    • 传递必要的参数:确保目标函数能够接收必要的参数,以完成其功能。
    • 返回值:如果需要,确保目标函数能够返回必要的值。
  4. 替换原始代码
    • 调用目标函数:在原始代码中,用调用目标函数的语句替换移动的代码块。
    • 清理未使用的变量:删除不再需要的局部变量或临时变量。
  5. 测试
    • 编译代码:确保代码编译通过,没有任何语法错误。
    • 运行测试:运行所有相关的单元测试,确保重构操作没有引入新的错误。
    • 手动测试:如果有必要,进行手动测试以验证功能的正确性。
  6. 代码审查
    • 同行评审:让同事或其他团队成员审查你的更改,确保代码质量和可维护性没有下降。
    • 文档更新:如果项目有维护文档的习惯,记得更新相关文档,说明移动语句的影响。

示例

假设有一个方法 processOrder,其中包含一段重复的计算逻辑,我们希望将其移动到已有的 calculateTotal 方法中:

public class Order {
    private double price;
    private int quantity;
    private double taxRate;
    private double shippingCost;

    public double calculateTotal() {
        double subTotal = price * quantity;
        double tax = subTotal * taxRate;
        double finalTotal = subTotal + tax;
        if (quantity > 10) {
            finalTotal += shippingCost;
        }
        return finalTotal;
    }

    public void processOrder() {
        double subTotal = price * quantity;
        double tax = subTotal * taxRate;
        double finalTotal = subTotal + tax;
        if (quantity > 10) {
            finalTotal += shippingCost;
        }
        System.out.println("Order total: " + finalTotal);
    }
}

步骤如下:

  1. 识别需要移动的代码块:
    • 重复的计算逻辑: double subTotal = price * quantity; double tax = subTotal * taxRate; double finalTotal = subTotal + tax; if (quantity > 10) { finalTotal += shippingCost; }
  2. 选择目标函数:
    • 选择 calculateTotal 方法作为目标函数。
  3. 移动代码块:
    • 将重复的计算逻辑移动到 calculateTotal 方法中。
  4. 替换原始代码:
    • processOrder 方法中,用调用 calculateTotal 方法的语句替换移动的代码块:

        public class Order {
          private double price;
          private int quantity;
          private double taxRate;
          private double shippingCost;
      
          public double calculateTotal() {
              double subTotal = price * quantity;
              double tax = subTotal * taxRate;
              double finalTotal = subTotal + tax;
              if (quantity > 10) {
                  finalTotal += shippingCost;
              }
              return finalTotal;
          }
      
          public void processOrder() {
              double finalTotal = calculateTotal();
              System.out.println("Order total: " + finalTotal);
          }
      }
      
  5. 测试:
    • 编译代码:确保代码编译通过,没有任何语法错误。
    • 运行测试:运行所有相关的单元测试,确保重构操作没有引入新的错误。
  6. 代码审查:
    • 让同事审查代码,确保没有引入新的问题。

练习

基础练习题

  1. 重复代码的移动
    • 给定以下 Java 代码,processUserInput 方法和 validateUserInput 方法都包含重复的代码块。请将重复的代码块移动到 validateInput 方法中。

        public class UserProcessor {
          private String userInput;
      
          public UserProcessor(String userInput) {
              this.userInput = userInput;
          }
      
          public void processUserInput() {
              if (validateInput(userInput)) {
                  System.out.println("Processing user input: " + userInput);
              } else {
                  System.out.println("Invalid user input.");
              }
          }
      
          public void validateUserInput() {
              if (validateInput(userInput)) {
                  System.out.println("User input is valid.");
              } else {
                  System.out.println("User input is invalid.");
              }
          }
      
          private boolean validateInput(String input) {
              return input != null && !input.isEmpty();
          }
      }
      
  2. 复杂逻辑块的移动
    • 下面的 Java 代码中,calculateRectangleAreacalculateSquareArea 方法都包含复杂的计算逻辑。请将计算逻辑移动到 calculateArea 方法中。

        public class ShapeCalculator {
          public void calculateRectangleArea(int length, int width) {
              int area = calculateArea(length, width);
              System.out.println("Rectangle area is: " + area);
          }
      
          public void calculateSquareArea(int side) {
              int area = calculateArea(side, side);
              System.out.println("Square area is: " + area);
          }
      
          private int calculateArea(int dim1, int dim2) {
              return dim1 * dim2;
          }
      }
      

进阶练习题

  1. 复杂逻辑块移动与参数传递
    • 在这段 Java 代码中,calculateShippingCost 方法内的逻辑较为复杂。请将计算折扣后的运费逻辑移动到 calculateDiscountedShippingCost 方法中,并正确处理参数传递。

        public class OrderShipping {
          private double baseShippingCost;
          private int orderQuantity;
          private double discountRate;
      
          public OrderShipping(double baseShippingCost, int orderQuantity, double discountRate) {
              this.baseShippingCost = baseShippingCost;
              this.orderQuantity = orderQuantity;
              this.discountRate = discountRate;
          }
      
          public double calculateShippingCost() {
              return calculateDiscountedShippingCost(baseShippingCost, orderQuantity, discountRate);
          }
      
          private double calculateDiscountedShippingCost(double baseCost, int quantity, double rate) {
              double discountAmount = baseCost * rate;
              double discountedShippingCost = baseCost - discountAmount;
              if (quantity > 5) {
                  discountedShippingCost -= 5;
              }
              return discountedShippingCost;
          }
      }
      
  2. 移动语句与返回值处理
    • 给定以下 Java 代码,processData 方法包含复杂逻辑。请将数据转换和计算结果的部分移动到 transformDatacalculateSum 方法中,并确保返回值正确处理。

        import java.util.ArrayList;
      import java.util.List;
      
      public class DataProcessor {
          public int processData(int[] dataArray) {
              List<Integer> processedData = transformData(dataArray);
              return calculateSum(processedData);
          }
      
          private List<Integer> transformData(int[] dataArray) {
              List<Integer> processedData = new ArrayList<>();
              for (int num : dataArray) {
                  num = num * 2;
                  if (num > 10) {
                      num = num - 5;
                  }
                  processedData.add(num);
              }
              return processedData;
          }
      
          private int calculateSum(List<Integer> processedData) {
              int sum = 0;
              for (int num : processedData) {
                  sum += num;
              }
              return sum;
          }
      }
      

综合拓展练习题

  1. 多模块移动与代码审查模拟
    • 考虑一个简单的 Java 电商系统,有 Product 类、Cart 类和 Order 类。Cart 类中的 calculateCartTotal 方法和 Order 类中的 calculateOrderTotal 方法都有重复且复杂的计算总价逻辑,同时 Cart 类中的 applyCartDiscount 方法逻辑也较为复杂。

    • 请对这些方法进行 “将语句移动到函数中” 重构,将重复和复杂逻辑移动到独立方法中。

    • 假设你完成了重构,请模拟一份简单的代码审查报告,指出重构后的优点和可能存在的潜在问题。

        class Product {
          private double price;
      
          public Product(double price) {
              this.price = price;
          }
      
          public double getPrice() {
              return price;
          }
      }
      
      class Cart {
          private Product[] products;
          private double discountRate;
      
          public Cart(Product[] products, double discountRate) {
              this.products = products;
              this.discountRate = discountRate;
          }
      
          public double calculateCartTotal() {
              double total = calculateSubtotal(products);
              return applyDiscount(total, discountRate);
          }
      
          public void applyCartDiscount() {
              if (products.length > 3) {
                  discountRate = adjustDiscountRate(discountRate, 0.05);
              }
              if (calculateCartTotal() > 100) {
                  discountRate = adjustDiscountRate(discountRate, 0.1);
              }
          }
      
          private double calculateSubtotal(Product[] products) {
              double total = 0;
              for (Product product : products) {
                  total += product.getPrice();
              }
              return total;
          }
      
          private double applyDiscount(double total, double rate) {
              double discountAmount = total * rate;
              return total - discountAmount;
          }
      
          private double adjustDiscountRate(double rate, double increment) {
              return rate + increment;
          }
      }
      
      class Order {
          private Product[] products;
          private double shippingCost;
      
          public Order(Product[] products, double shippingCost) {
              this.products = products;
              this.shippingCost = shippingCost;
          }
      
          public double calculateOrderTotal() {
              double total = calculateSubtotal(products);
              return total + shippingCost;
          }
      
          private double calculateSubtotal(Product[] products) {
              double total = 0;
              for (Product product : products) {
                  total += product.getPrice();
              }
              return total;
          }
      }