C++遗留代码重构:逐步指南

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

重构 C++ 遗留代码:如何将“动物园”变成可维护的项目

处理 C++ 遗留代码是许多开发人员不可避免的现实。此类代码通常缺乏测试,充斥着未定义行为、全局变量和手动内存管理。然而,重构 C++ 遗留代码不仅仅是“梳理”文件,而是一个提高可靠性和性能的系统性过程。在本文中,我们将分解关键步骤,帮助您安全地现代化旧项目,而不会冒破坏一切的风险。

1. 准备:测试与隔离

第一步也是最重要的一步是创建一张“安全网”。没有测试,任何重构都会变成一场俄罗斯轮盘赌。

  • 特征测试:编写能够捕获代码当前行为的测试。即使它看起来是错误的,也要先将其固定下来。
  • 静态分析:使用诸如 clang-tidy 或 Cppcheck 之类的工具。它们会识别潜在的内存泄漏、未使用的代码和风格违规。
  • Sanitizers:使用 AddressSanitizer 和 UndefinedBehaviorSanitizer 运行代码。这有助于在开始更改之前发现隐藏的未定义行为。

在 CMake 中添加 sanitizer 的示例:

# CMakeLists.txt
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer")

2. 摆脱“裸”指针和手动内存管理

遗留 C++ 经常遭受内存泄漏和双重删除的困扰。现代 C++(C++11 及更新版本)提供了 RAII 包装器。

  • 用智能指针替换 new/delete:使用 std::unique_ptr 实现独占所有权,使用 std::shared_ptr 实现共享所有权。
  • 使用 std::vector 代替原始数组:这降低了越界访问的风险。

重构示例:

之前:

class LegacyBuffer {
    char* data;
    size_t size;
public:
    LegacyBuffer(size_t sz) : size(sz) {
        data = new char[sz];
    }
    ~LegacyBuffer() { delete[] data; }
    // 缺少拷贝构造函数和赋值运算符(违反“三法则”)
};

之后:

class ModernBuffer {
    std::vector<char> data;
public:
    ModernBuffer(size_t sz) : data(sz) {}
    // 析构函数、拷贝和赋值由编译器自动生成
};

3. 消除宏和全局状态

宏(#define)是难以追踪的错误的常见原因。它们应该被替换为 constexpr、enum class 或模板。全局变量使测试和并行化变得复杂。

  • 将 #define BUFFER_SIZE 1024 替换为 constexpr size_t BUFFER_SIZE = 1024;
  • 封装全局变量:将全局状态提取到单例类中,或者更好的方法是,通过构造函数传递依赖项。

宏示例:

// 不好:宏没有作用域
#define MAX_CONNECTIONS 100

// 好:具有类型和作用域的常量
namespace Config {
    constexpr int maxConnections = 100;
}

使用 enum class 代替 #define 作为标志:

// 代替 #define FLAG_READ 1
enum class FileMode : uint8_t {
    Read = 1,
    Write = 2,
    ReadWrite = 3
};

4. 简化复杂函数并删除死代码

遗留函数通常是 500 多行代码的“床单”,包含许多嵌套条件。策略:

  • 提取方法:将

博客

书籍推荐