重构手法——函数行为重塑类 | 语意 | 用函数替换重复代码

80 阅读2分钟

简介

"用函数替换重复代码"是最常用的重构手法之一。通过将重复出现的代码片段提取为独立函数,可以提升代码复用性、降低维护成本。此重构特别适用于消除代码的坏味道"Duplicated Code"。

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

  • 完全相同的代码块出现在多个位置
  • 结构相似但变量名不同的代码片段
  • 通过复制粘贴实现的相似功能

用函数替换重复代码的详细步骤

  1. 识别重复模式
    • 使用代码对比工具或人工审查找到重复代码
    • 确认重复代码的功能一致性
  2. 参数化差异点
    • 识别需要变化的变量和常量
    • 确定函数参数列表
  3. 创建新函数
    • 使用明确反映功能的方法命名
    • 处理返回值类型和异常情况
  4. 替换重复代码
    • 用函数调用替换原始代码块
    • 保持原有代码的接口兼容性

示例

重构前代码:

class ReportGenerator {
    void generatePDFReport(Data data) {
        // Header
        System.out.println("Report Header");
        System.out.println("Generated: " + new Date());

        // Data section
        for (Item item : data.getItems()) {
            System.out.printf("| %-15s | %10.2f |%n", item.name(), item.value());
        }

        // Footer
        System.out.println("Report Footer");
    }

    void generateHTMLReport(Data data) {
        // Header
        System.out.println("<header>Report Header</header>");
        System.out.println("<time>" + new Date() + "</time>");

        // Data section
        System.out.println("<table>");
        for (Item item : data.getItems()) {
            System.out.printf("<tr><td>%s</td><td>%.2f</td></tr>%n", item.name(), item.value());
        }
        System.out.println("</table>");

        // Footer
        System.out.println("<footer>Report Footer</footer>");
    }
}

重构步骤:

  1. 提取公共的Header生成逻辑:

    private void generateHeader(String format) {
        if (format.equals("PDF")) {
            System.out.println("Report Header");
            System.out.println("Generated: " + new Date());
        } else {
            System.out.println("<header>Report Header</header>");
            System.out.println("<time>" + new Date() + "</time>");
        }
    }
    
  2. 参数化报表内容生成:

    private void generateContent(String format, Data data) {
        if (format.equals("PDF")) {
            for (Item item : data.getItems()) {
                System.out.printf("| %-15s | %10.2f |%n", item.name(), item.value());
            }
        } else {
            System.out.println("<table>");
            for (Item item : data.getItems()) {
                System.out.printf("<tr><td>%s</td><td>%.2f</td></tr>%n", item.name(), item.value());
            }
            System.out.println("</table>");
        }
    }
    
  3. 统一报表生成入口:

    public void generateReport(String format, Data data) {
        generateHeader(format);
        generateContent(format, data);
        generateFooter(format);
    }
    
    private void generateFooter(String format) {
        if (format.equals("PDF")) {
            System.out.println("Report Footer");
        } else {
            System.out.println("<footer>Report Footer</footer>");
        }
    }
    

练习

基础练习题

  1. 简单重复代码提取

    // 重构前
    class Calculator {
        void add(int a, int b) {
            System.out.println("Start calculation...");
            System.out.println(a + " + " + b + " = " + (a + b));
        }
    
        void multiply(int a, int b) {
            System.out.println("Start calculation...");
            System.out.println(a + " * " + b + " = " + (a * b));
        }
    }
    

进阶练习题

  1. 参数化模板方法

    // 重构前
    class DataExporter {
        String exportCSV(List<Data> dataList) {
            StringBuilder sb = new StringBuilder();
            sb.append("name,age,score\n");
            for (Data d : dataList) {
                sb.append(d.name).append(",")
                        .append(d.age).append(",")
                        .append(d.score).append("\n");
            }
            return sb.toString();
        }
    
        String exportHTML(List<Data> dataList) {
            StringBuilder sb = new StringBuilder();
            sb.append("<table>\n<tr><th>Name</th><th>Age</th><th>Score</th></tr>\n");
            for (Data d : dataList) {
                sb.append("<tr><td>").append(d.name).append("</td>")
                        .append("<td>").append(d.age).append("</td>")
                        .append("<td>").append(d.score).append("</td></tr>\n");
            }
            sb.append("</table>");
            return sb.toString();
        }
    }
    

综合拓展练习题

  1. 复杂业务逻辑重构

    // 重构前
    class OrderProcessor {
        void processDomesticOrder(Order order) {
            validateAddress(order);
            checkInventory(order);
            applyDomesticTax(order);
            generateInvoice(order);
            notifyShippingDepartment(order);
        }
    
        void processInternationalOrder(Order order) {
            validateAddress(order);
            checkInventory(order);
            applyCustomsDuty(order);
            generateInvoice(order);
            notifyShippingDepartment(order);
        }
    }
    

代码审查要点

  1. 优点:

    • 降低代码重复率
    • 统一业务逻辑处理
    • 提升可测试性
  2. 潜在问题:

    • 避免过度抽象导致方法参数爆炸
    • 注意提取函数的单一职责原则
    • 保持合理的抽象层级