WEBKT

如何重构包含大量全局变量的遗留代码?

60 0 0 0

问题:老模块全局变量满天飞,修改外部状态如履薄冰,怎么办?

最近在重构一个老旧的业务模块,发现代码里充斥着各种全局变量和函数对外部状态的直接修改。每次改动都如履薄冰,生怕引入新的 bug。有什么具体的方法能让这种“牵一发而动全身”的情况得到改善吗?我需要的是实打实的代码实践指导,而不是泛泛而谈的理论。

回答:逐步拆解全局状态,拥抱函数式编程思想

你的问题非常典型,很多老项目都存在类似的问题。全局变量和副作用是代码维护的噩梦,重构时需要特别小心。下面我提供一些实战指导,希望能帮助你逐步改善这种情况:

1. 识别和隔离全局变量:

  • 第一步:梳理。花时间梳理模块中的所有全局变量,列出它们的用途、影响范围以及被哪些函数访问和修改。可以使用代码搜索工具(如 grep)来辅助查找。
  • 第二步:封装。将全局变量封装成类的静态成员变量或单例模式。这样至少可以控制访问方式,避免随意修改。
```java
// 原始代码
public static int globalCounter = 0;

public static void incrementCounter() {
    globalCounter++;
}

// 封装后的代码
public class Counter {
    private static int counter = 0;

    public static int getCounter() {
        return counter;
    }

    public static void incrementCounter() {
        counter++;
    }
}
```

2. 消除副作用:

  • 识别副作用。找出所有修改外部状态的函数。这些函数通常会修改全局变量、写入文件、发送网络请求等。
  • 尽可能使用纯函数。纯函数是指没有副作用的函数,即给定相同的输入,总是返回相同的输出,并且不会修改任何外部状态。将带有副作用的函数重构为纯函数。
```java
// 原始代码(有副作用)
public static void updateUserStatus(User user, String status) {
    user.setStatus(status); // 修改了 user 对象的状态
    database.update(user);   // 修改了数据库
}

// 重构后的代码(尽可能消除副作用)
public static User createUpdatedUser(User user, String status) {
    User newUser = user.clone(); // 克隆 user 对象
    newUser.setStatus(status);
    return newUser;
}

// 调用方负责更新数据库
User updatedUser = createUpdatedUser(user, "active");
database.update(updatedUser);
```
  • 使用不可变数据结构。如果必须修改状态,尽量使用不可变数据结构。每次修改都创建一个新的对象,而不是修改原始对象。这可以避免意外的副作用。Java 中可以使用 ImmutableListImmutableMap 等 Guava 库提供的不可变集合。

3. 依赖注入:

  • 避免硬编码依赖。不要在函数内部直接创建依赖的对象,而是通过参数传递进来。这可以提高代码的可测试性和可维护性。
```java
// 原始代码(硬编码依赖)
public class UserService {
    private Database database = new Database();

    public User getUser(int id) {
        return database.getUserById(id);
    }
}

// 重构后的代码(依赖注入)
public class UserService {
    private Database database;

    public UserService(Database database) {
        this.database = database;
    }

    public User getUser(int id) {
        return database.getUserById(id);
    }
}

// 使用时注入依赖
Database database = new Database();
UserService userService = new UserService(database);
```

4. 控制状态变化:

  • 使用状态管理模式。如果状态变化比较复杂,可以考虑使用状态模式、有限状态机等设计模式来控制状态变化。
  • 事件驱动架构。将状态变化抽象成事件,使用事件总线来传递事件。这可以解耦状态变化和业务逻辑。

5. 测试先行:

  • 编写单元测试。在重构之前,先为现有的代码编写单元测试。这可以确保重构后的代码仍然能够正常工作。
  • 持续集成。使用持续集成工具,每次提交代码都自动运行单元测试。这可以尽早发现问题。

重要提示:

  • 循序渐进:不要试图一次性解决所有问题。每次只重构一小部分代码,并确保每次重构后代码仍然能够正常工作。
  • 代码审查:请同事帮忙审查你的代码。这可以帮助你发现潜在的问题。
  • 文档记录:记录你的重构过程和遇到的问题。这可以帮助你和其他人理解代码。

重构是一个漫长的过程,需要耐心和毅力。希望这些建议能帮助你成功地重构你的老模块。记住,关键在于逐步拆解全局状态,拥抱函数式编程思想,并始终保持测试先行。

重构专家 代码重构全局变量函数式编程

评论点评