重构手法——数据重组类 | 数据关系重构 | 将预计移动到调用者处

57 阅读3分钟

简介

“将语句移动到调用者处”(Move Statements to Callers)是一种重构手法,通过将方法内部的某些语句移动到调用该方法的位置,来提高代码的灵活性和职责单一性。这种重构适用于方法中存在与核心逻辑耦合度较低的代码,或需要不同调用者差异化处理的场景。

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

  • 方法承担了多个职责(Multifaceted Method)
  • 方法内部包含调用者特有的逻辑(Caller-Specific Logic in Method)
  • 代码块在不同调用场景中需要差异化执行(Divergent Behavior in Calls)

将语句移动到调用者处(Move Statements to Callers)的详细步骤

  1. 识别需要移动的代码块
    • 寻找方法中与核心逻辑关联度低的代码
    • 识别调用者可能需要定制/控制的逻辑片段
    • 评估代码移动后的影响范围
  2. 调整原始方法
    • 将目标代码块从原方法中移除
    • 添加必要的返回值或参数
    • 确保原方法保持功能完整性
  3. 修改调用点
    • 在调用位置添加被移出的代码
    • 处理返回值或参数传递
    • 保持原有业务逻辑不变
  4. 测试验证
    • 运行单元测试验证功能正确性
    • 特别注意多个调用点的差异性
  5. 代码审查
    • 检查是否引入重复代码
    • 验证职责划分合理性

示例

原始代码:

class OrderProcessor {
    public void processOrder(Order order) {
        double total = calculateTotal(order);
        // 需要移动的日志语句
        System.out.println("Order processed: " + order.getId());
        applyTax(total);
    }
    
    private double calculateTotal(Order order) { }
    private void applyTax(double amount) {  }
}

重构步骤:

  1. 识别需要移动的日志语句
  2. 从 processOrder 方法中移除日志代码
  3. 在每个调用点添加日志语句

重构后:

class OrderProcessor {
   // 修改后的方法
   public void processOrder(Order order) {
      double total = calculateTotal(order);
      applyTax(total);
   }

   // 新增公共方法供调用者获取计算结果
   public double getCalculatedTotal(Order order) {
      return calculateTotal(order);
   }

   // 其他方法保持不变...
}

// 调用方代码
class Main {
   public static void executeOrder(Order order) {
      OrderProcessor processor = new OrderProcessor();
      processor.processOrder(order);
      // 移动过来的日志语句
      System.out.println("Order processed: " + order.getId());
   }
}

练习

基础练习题

  1. 移动日志语句

    • 将 validateUser 方法中的日志输出移动到调用者
    class UserValidator {
       public boolean validateUser(User user) {
          boolean isValid = checkCredentials(user);
          System.out.println("Validation result: " + isValid); // 需要移动
          return isValid;
       }
    }
    

进阶练习题

  1. 条件语句移动

    • 将 calculateDiscount 方法中的 VIP 检查逻辑移动到调用者
    class PriceCalculator {
       public double calculateDiscount(User user, double amount) {
          if (user.isVIP()) { // 需要移动的条件判断
             return amount * 0.2;
          }
          return amount * 0.1;
       }
    }
    

综合拓展练习题

  1. 多调用点差异化处理

    • 重构 ReportGenerator 使其格式设置逻辑由不同调用者控制
    class ReportGenerator {
       public String generateReport(Data data) {
          String content = buildContent(data);
          // 需要移动的格式设置
          return "<html><body>" + content + "</body></html>";
       }
    
       // 其他报告生成方法...
    }
    

代码审查要点

  1. 审查维度

    • 单一职责原则:检查方法是否更聚焦核心职责
    • 重复代码风险:评估多个调用点的代码重复可能性
    • 接口合理性:验证新增参数/返回值的必要性
  2. 典型问题

    • 过度移动导致调用点复杂度上升
    • 未正确处理移动后的异常处理
    • 多个调用点出现重复代码
  3. 优化建议

    • 对多个调用点的公共逻辑使用「提取方法」
    • 使用模板方法模式处理差异化需求
    • 加强调用点的单元测试覆盖