2Working Effectively with Legacy Code

接缝技术

代码接缝

接缝类型依赖分解头文件接缝链接接缝

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第2章:接缝技术(Seam Techniques)

一、导读

1.1 本章学习目标

第2章讲解接缝技术——Feathers 最核心的贡献。接缝是代码中可以插入测试或修改行为的点。

本章的核心学习目标包括:

  • 理解接缝的概念和类型
  • 掌握头文件接缝、链接接缝、预处理接缝
  • 学会在 Java/C++ 中使用接缝
  • 理解接缝与依赖分解的关系

1.2 为什么本章如此重要

接缝技术是处理遗留代码的核心工具。通过接缝,你可以在不修改原始代码的情况下替换依赖、插入测试、隔离行为。

1.3 核心问题引导

什么是接缝?它和"注入点"有什么区别?

接缝有哪些类型?各适用于什么场景?

如何识别代码中的接缝?

接缝如何帮助分解依赖?


二、核心概念详解

2.1 接缝的定义

接缝(Seam):程序中可以不改变行为而替换行为的点

接缝类型:
1. 预处理器接缝:通过预处理指令替换代码
2. 链接接缝:通过链接不同的实现替换代码
3. 对象接缝:通过多态替换对象行为
4. 子类接缝:通过继承替换行为

2.2 对象接缝

java
// 遗留代码
public class OrderProcessor {
    private EmailService emailService = new EmailService();
    
    public void process(Order order) {
        // ... 处理订单
        emailService.sendConfirmation(order.getCustomer());
    }
}

// 问题:EmailService 是硬编码的,无法测试

// 使用接缝:提取接口
public interface EmailSender {
    void sendConfirmation(Customer customer);
}

public class OrderProcessor {
    private EmailSender emailSender;
    
    // 接缝:构造函数注入
    public OrderProcessor(EmailSender emailSender) {
        this.emailSender = emailSender;
    }
    
    // 保持向后兼容
    public OrderProcessor() {
        this(new EmailService());
    }
    
    public void process(Order order) {
        // ... 处理订单
        emailSender.sendConfirmation(order.getCustomer());
    }
}

// 测试中使用 mock
@Test
void testProcess() {
    List<String> sentEmails = new ArrayList<>();
    EmailSender mockSender = customer -> sentEmails.add(customer.getEmail());
    
    OrderProcessor processor = new OrderProcessor(mockSender);
    processor.process(new Order(new Customer("test@example.com")));
    
    assertEquals(1, sentEmails.size());
}

2.3 子类接缝

java
// 遗留代码:无法修改
public class LegacySystem {
    public String getData() {
        // 调用外部服务,测试时不可用
        return ExternalService.fetch();
    }
}

// 使用子类接缝
public class TestableSystem extends LegacySystem {
    private String testData;
    
    public void setTestData(String data) {
        this.testData = data;
    }
    
    @Override
    public String getData() {
        return testData != null ? testData : super.getData();
    }
}

// 测试中使用
@Test
void testWithSubclass() {
    TestableSystem system = new TestableSystem();
    system.setTestData("test data");
    // 现在可以测试了
}

2.4 链接接缝

c
// 遗留 C 代码
void process() {
    time_t now = time(NULL);
    // ... 使用 now
}

// 链接接缝:替换 time 函数
// 生产代码链接系统 time
// 测试代码链接自定义 time
time_t custom_time(time_t *t) {
    return test_time;    // 可控的时间
}

三、实战案例

3.1 案例一:识别和应用接缝

java
// 遗留代码
public class ReportGenerator {
    public String generate() {
        Connection conn = DriverManager.getConnection(DB_URL);    // 硬编码依赖
        Calendar cal = Calendar.getInstance();                      // 时间依赖
        String user = System.getProperty("user.name");             // 环境依赖
        // ...
    }
}

// 识别接缝:
// 1. DriverManager.getConnection → 数据库接缝
// 2. Calendar.getInstance → 时间接缝
// 3. System.getProperty → 环境接缝

// 应用接缝
public class ReportGenerator {
    private DataSource dataSource;
    private Clock clock;
    private Environment env;
    
    public ReportGenerator(DataSource ds, Clock clock, Environment env) {
        this.dataSource = ds;
        this.clock = clock;
        this.env = env;
    }
    
    // 默认构造函数保持兼容
    public ReportGenerator() {
        this(new JdbcDataSource(DB_URL), SystemClock.INSTANCE, SystemEnvironment.INSTANCE);
    }
}

四、本章小结

接缝:可以不改变行为而替换行为的点。

对象接缝:通过接口和多态替换依赖。

子类接缝:通过继承覆盖方法。

链接接缝:通过链接不同的实现。


五、思考与练习

在一个遗留系统中找出至少5个接缝。

子类接缝有什么风险?如何最小化这些风险?

如何在不修改原始代码的情况下添加接缝?


2/7接缝技术
0%