可维护性 可扩展性 可复用性

实际项目中 c++针对这三种特性都有哪些常用方法?

好问题。我来梳理一下实际项目中真正常用的方法,而不是教科书上的理论。我会用最基础的 C++ 语法,每行注释。


一、可维护性(Maintainability)------ 实际项目常用方法

方法1:拆分函数(最基础、最常用)

原则:一个函数不超过 50 行,一个函数只做一件事

复制代码
// ❌ 坏:一个函数 200 行,什么都做
void processOrder(const json& data) {
    // 第1-30行:解析数据
    // 第31-50行:校验数据
    // 第51-80行:计算价格
    // 第81-100行:检查库存
    // 第101-130行:生成订单
    // 第131-150行:发送邮件
    // 第151-180行:记录日志
    // 第181-200行:返回结果
}

// ✅ 好:拆成多个小函数
Order parseOrderData(const json& data);       // 只负责解析
bool validateOrder(const Order& order);        // 只负责校验
double calculatePrice(const Order& order);     // 只负责计算
bool checkStock(const Order& order);           // 只负责检查
OrderId generateOrder(const Order& order);     // 只负责生成
void sendEmail(const Order& order);            // 只负责发送
void logOrder(const Order& order);             // 只负责记录

实际项目中怎么用:

  • 团队一般约定:一个函数不要超过一屏(约 50 行)
  • 如果超过,就拆
  • 函数名要能说清楚"我做了什么"

方法2:常量命名(防止"魔法数字")

复制代码
// ❌ 坏:不知道 60、4096、3 是什么意思
if (age > 60) { ... }
buffer = new char[4096];
if (retryCount > 3) { ... }

// ✅ 好:用常量或枚举
const int MAX_RETRY_COUNT = 3;
const int MAX_NAME_LENGTH = 50;
const int BUFFER_SIZE = 4096;
const int RETIREMENT_AGE = 60;

if (age > RETIREMENT_AGE) { ... }
buffer = new char[BUFFER_SIZE];
if (retryCount > MAX_RETRY_COUNT) { ... }

实际项目中怎么用:

  • 文件开头定义常量
  • 或者用 const 全局变量
  • 或者用 #define(但尽量用 const)

方法3:尽早返回(减少嵌套)

复制代码
// ❌ 坏:嵌套太深,逻辑绕
void process(const json& data) {
    if (data.contains("order")) {
        if (data["order"].contains("id")) {
            if (data["order"]["id"].is_string()) {
                // 这里才真正开始处理
                // ...
            }
        }
    }
}

// ✅ 好:尽早返回,减少嵌套
void process(const json& data) {
    // 检查条件,不满足就返回
    if (!data.contains("order")) {
        return;  // 没有 order 字段,直接返回
    }
    
    if (!data["order"].contains("id")) {
        return;  // 没有 id 字段,直接返回
    }
    
    if (!data["order"]["id"].is_string()) {
        return;  // id 不是字符串,直接返回
    }
    
    // 到这里,所有条件都满足了,开始处理
    // ...
}

实际项目中怎么用:

  • 先检查所有错误条件,尽早返回
  • 最后才写正常逻辑
  • 这样可以减少 if-else 的嵌套层数

方法4:用枚举代替字符串比较

复制代码
// ❌ 坏:字符串比较,容易写错
if (type == "user") { ... }      // 如果写成 "uesr" 就 bug 了
else if (type == "order") { ... }

// ✅ 好:用枚举,编译器会检查
enum MessageType {
    TYPE_USER,    // 0
    TYPE_ORDER,   // 1
    TYPE_PRODUCT, // 2
    TYPE_COUNT    // 3(用于数组大小)
};

// 然后可以用 switch
switch (type) {
    case TYPE_USER:
        // ...
        break;
    case TYPE_ORDER:
        // ...
        break;
    // 编译器会警告:如果少了某个 case
}

实际项目中怎么用:

  • 如果类型是固定的(比如不超过 10 种),用枚举
  • 如果类型是动态的(比如用户自定义),用 map

二、可扩展性(Extensibility)------ 实际项目常用方法

方法1:函数指针/回调(最常用)

复制代码
// ========== 实际项目中最常用的扩展方式 ==========

// 1. 定义一个函数指针类型
typedef int (*Callback)(int data);

// 2. 写一个处理函数,接受回调
void processData(int* array, int size, Callback callback) {
    for (int i = 0; i < size; i++) {
        array[i] = callback(array[i]);  // 调用回调
    }
}

// 3. 不同的调用方提供不同的回调
int doubleValue(int x) { return x * 2; }
int squareValue(int x) { return x * x; }
int addTen(int x) { return x + 10; }

// 4. 使用
int data[] = {1, 2, 3, 4, 5};
processData(data, 5, doubleValue);  // 翻倍
processData(data, 5, squareValue);  // 平方
processData(data, 5, addTen);       // 加10
// 不需要改 processData 函数本身

实际项目中怎么用:

  • 排序函数:可以传入不同的比较函数
  • 遍历函数:可以传入不同的处理函数
  • 网络库:可以传入不同的回调(收到数据做什么)

方法2:虚函数 + 继承(面向对象方式)

复制代码
// ========== 实际项目中:插件系统、UI 框架 ==========

// 1. 定义接口
class IPlugin {
public:
    virtual ~IPlugin() {}
    virtual void init() = 0;           // 初始化
    virtual void execute() = 0;        // 执行
    virtual void cleanup() = 0;        // 清理
    virtual const char* getName() = 0; // 插件名
};

// 2. 不同的插件
class LogPlugin : public IPlugin {
public:
    void init() { /* 初始化日志系统 */ }
    void execute() { /* 写日志 */ }
    void cleanup() { /* 关闭日志文件 */ }
    const char* getName() { return "LogPlugin"; }
};

class MonitorPlugin : public IPlugin {
public:
    void init() { /* 初始化监控 */ }
    void execute() { /* 收集性能数据 */ }
    void cleanup() { /* 停止监控 */ }
    const char* getName() { return "MonitorPlugin"; }
};

// 3. 管理器(不用改,支持任意插件)
class PluginManager {
private:
    IPlugin* plugins[10];  // 最多支持10个插件
    int count;
    
public:
    PluginManager() : count(0) {}
    
    // 注册插件(扩展点)
    void registerPlugin(IPlugin* plugin) {
        if (count < 10) {
            plugins[count++] = plugin;
            plugin->init();  // 自动初始化
        }
    }
    
    // 执行所有插件
    void executeAll() {
        for (int i = 0; i < count; i++) {
            plugins[i]->execute();
        }
    }
};

// 4. 使用
int main() {
    PluginManager manager;
    
    // 注册插件(加新插件只需要 new 一个对象 + 注册)
    manager.registerPlugin(new LogPlugin());
    manager.registerPlugin(new MonitorPlugin());
    
    // 执行
    manager.executeAll();
}

实际项目中怎么用:

  • 游戏引擎:不同的角色继承自同一个基类
  • UI 框架:不同的控件继承自同一个基类
  • 插件系统:所有插件实现同一个接口

方法3:模板(编译期扩展)

复制代码
// ========== 实际项目中:容器、算法库 ==========

// 1. 模板函数:可以处理任意类型
template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

// 2. 使用
int a = max(3, 5);          // T = int
double b = max(3.14, 2.56); // T = double
std::string c = max(std::string("abc"), std::string("xyz")); // T = string

// 不需要为每种类型重写

实际项目中怎么用:

  • STL 容器std::vector<int>std::vector<string>
  • 算法std::sortstd::find
  • 不需要为每种类型重写同样的逻辑

方法4:策略模式(把"变化的部分"封装起来)

复制代码
// ========== 实际项目中:压缩、加密、序列化 ==========

// 1. 定义策略接口
class ICompressionStrategy {
public:
    virtual ~ICompressionStrategy() {}
    virtual std::string compress(const std::string& data) = 0;
    virtual std::string decompress(const std::string& data) = 0;
};

// 2. 不同的策略
class NoCompression : public ICompressionStrategy {
public:
    std::string compress(const std::string& data) { return data; }
    std::string decompress(const std::string& data) { return data; }
};

class GzipCompression : public ICompressionStrategy {
public:
    std::string compress(const std::string& data) { /* gzip 压缩 */ }
    std::string decompress(const std::string& data) { /* gzip 解压 */ }
};

class ZipCompression : public ICompressionStrategy {
public:
    std::string compress(const std::string& data) { /* zip 压缩 */ }
    std::string decompress(const std::string& data) { /* zip 解压 */ }
};

// 3. 使用策略的类
class FileSaver {
private:
    ICompressionStrategy* compressor_;  // 策略对象
    
public:
    // 可以在运行时切换策略
    void setCompression(ICompressionStrategy* compressor) {
        compressor_ = compressor;
    }
    
    void save(const std::string& filename, const std::string& data) {
        std::string compressed = compressor_->compress(data);
        // 保存到文件...
    }
};

// 4. 使用
int main() {
    FileSaver saver;
    
    // 使用不同的压缩策略,不需要改 FileSaver 的代码
    saver.setCompression(new NoCompression());
    saver.save("test.txt", "hello");
    
    saver.setCompression(new GzipCompression());
    saver.save("test.gz", "hello");
}

实际项目中怎么用:

  • 压缩算法:可以切换 zip、gzip、7z
  • 加密算法:可以切换 AES、DES、RSA
  • 序列化格式:可以切换 JSON、XML、Protobuf

三、可复用性(Reusability)------ 实际项目常用方法

方法1:封装成独立模块(最常用)

复制代码
// ========== 实际项目:把通用功能封装成类 ==========

// 1. 封装一个"日志模块"
// 这个模块可以放在任何项目里使用
class Logger {
private:
    FILE* file_;
    bool enabled_;
    
public:
    Logger(const char* filename) {
        file_ = fopen(filename, "a");
        enabled_ = true;
    }
    
    ~Logger() {
        if (file_) fclose(file_);
    }
    
    void log(const char* level, const char* message) {
        if (!enabled_ || !file_) return;
        
        // 获取当前时间
        time_t now = time(NULL);
        char* timeStr = ctime(&now);
        // 去掉换行符
        timeStr[strlen(timeStr) - 1] = '\0';
        
        fprintf(file_, "[%s] [%s] %s\n", timeStr, level, message);
        fflush(file_);
    }
    
    void info(const char* msg) { log("INFO", msg); }
    void warn(const char* msg) { log("WARN", msg); }
    void error(const char* msg) { log("ERROR", msg); }
    
    void setEnabled(bool enabled) { enabled_ = enabled; }
};

// 2. 任何项目都可以直接使用
// 项目A
Logger logger("app.log");
logger.info("项目A 启动成功");

// 项目B(同一个 Logger 类)
Logger logger("server.log");
logger.info("项目B 开始处理请求");

实际项目中怎么用:

  • 公司内部一般有公共库,放这些通用模块
  • 日志、配置、网络、文件操作等,都封装成独立模块
  • 新项目直接引用,不用重复造轮子

方法2:用参数化代替硬编码

复制代码
// ❌ 坏:硬编码,不能复用
void processFile() {
    const char* filename = "C:\\data\\input.txt";
    int bufferSize = 1024;
    const char* encoding = "UTF-8";
    // ...
}

// ✅ 好:参数化,可以复用
// 这个函数可以处理任何文件
void processFile(
    const char* filename,    // 文件名(可配置)
    int bufferSize,          // 缓冲区大小(可配置)
    const char* encoding     // 编码(可配置)
) {
    // ...
}

实际项目中怎么用:

  • 不要硬编码路径、大小、数量
  • 用参数传进来
  • 或者用配置文件读取

方法3:模板 + 类型参数

复制代码
// ========== 实际项目:通用容器、通用算法 ==========

// 1. 写一个通用的"缓存"类
template <typename T>
class Cache {
private:
    T* data_;
    int capacity_;
    int size_;
    
public:
    Cache(int capacity) : capacity_(capacity), size_(0) {
        data_ = new T[capacity];
    }
    
    ~Cache() { delete[] data_; }
    
    void add(const T& item) {
        if (size_ < capacity_) {
            data_[size_++] = item;
        }
    }
    
    T get(int index) {
        if (index >= 0 && index < size_) {
            return data_[index];
        }
        return T();  // 返回默认值
    }
    
    int size() { return size_; }
};

// 2. 可以缓存任何类型
Cache<int> intCache(100);           // 缓存 int
Cache<std::string> strCache(50);    // 缓存 string
Cache<Order> orderCache(20);        // 缓存 Order 对象

实际项目中怎么用:

  • STL 容器都是模板:vectormapset
  • 自己写通用类时,也尽量用模板

方法4:组合优于继承

复制代码
// ========== 实际项目:通过组合复用功能 ==========

// 1. 先写一些小的、可复用的功能类
class Logger {
public:
    void log(const char* msg) { /* ... */ }
};

class Database {
public:
    void save(const char* data) { /* ... */ }
};

class EmailSender {
public:
    void send(const char* to, const char* content) { /* ... */ }
};

// 2. 通过组合使用它们(而不是继承)
class OrderProcessor {
private:
    // 组合:把需要的功能"组合"进来
    Logger logger_;
    Database db_;
    EmailSender email_;
    
public:
    void process(const Order& order) {
        logger_.log("开始处理订单");     // 复用日志功能
        db_.save("order data...");       // 复用数据库功能
        email_.send("user@test.com", "订单已处理");  // 复用邮件功能
        logger_.log("订单处理完成");     // 复用日志功能
    }
};

实际项目中怎么用:

  • 不要为了复用而继承(比如"订单处理器继承自日志器"是不对的)
  • 把功能封装成小类,然后通过组合使用

四、总结:实际项目中最常用的方法速查表

表格

目标 最常用方法 适用场景 复杂度
可维护性 拆分函数 所有场景 ⭐ 低
可维护性 尽早返回 减少嵌套 ⭐ 低
可维护性 用枚举代替魔法数字 固定类型 ⭐ 低
可扩展性 函数指针/回调 变化的行为 ⭐⭐ 中
可扩展性 虚函数+继承 插件系统、UI ⭐⭐ 中
可扩展性 模板 通用算法 ⭐⭐⭐ 高
可复用性 封装独立模块 日志、网络、文件 ⭐⭐ 中
可复用性 参数化 避免硬编码 ⭐ 低
可复用性 组合 功能复用 ⭐⭐ 中

五、一个完整的实际项目示例

假设你要写一个数据导入工具,支持从不同格式(CSV、JSON、XML)导入数据。

复制代码
// ========== 完整的示例:展示三者的结合 ==========

// 1. 可复用性:封装成独立模块(日志模块)
class Logger {
public:
    void info(const char* msg) { 
        printf("[INFO] %s\n", msg); 
    }
    void error(const char* msg) { 
        printf("[ERROR] %s\n", msg); 
    }
};

// 2. 可扩展性:定义接口,支持不同格式
class IDataReader {
public:
    virtual ~IDataReader() {}
    virtual bool open(const char* filename) = 0;
    virtual bool readRow(std::string* columns, int colCount) = 0;
    virtual void close() = 0;
};

// CSV 读取器
class CsvReader : public IDataReader {
private:
    FILE* file_;
public:
    bool open(const char* filename) {
        file_ = fopen(filename, "r");
        return file_ != NULL;
    }
    bool readRow(std::string* columns, int colCount) {
        // 读取一行,按逗号分割
        // ...
    }
    void close() {
        if (file_) fclose(file_);
    }
};

// JSON 读取器
class JsonReader : public IDataReader {
    // 类似的实现...
};

// 3. 可维护性:主函数简洁明了
class DataImporter {
private:
    Logger logger_;          // 日志(可复用)
    IDataReader* reader_;    // 读取器(可扩展)
    
public:
    void setReader(IDataReader* reader) {
        reader_ = reader;    // 可以在运行时切换
    }
    
    bool import(const char* filename) {
        logger_.info("开始导入数据");
        
        // 尽早返回(可维护性)
        if (!reader_->open(filename)) {
            logger_.error("无法打开文件");
            return false;
        }
        
        // 读取数据
        std::string columns[10];
        int rowCount = 0;
        while (reader_->readRow(columns, 10)) {
            // 处理每一行...
            rowCount++;
        }
        
        reader_->close();
        logger_.info("导入完成,共处理");
        
        // 用 char 数组拼接数字
        char countMsg[50];
        sprintf(countMsg, "导入完成,共处理 %d 行", rowCount);
        logger_.info(countMsg);
        
        return true;
    }
};

这个例子同时体现了:

表格

特性 体现在哪里
可维护性 函数简短、尽早返回、日志清晰
可扩展性 可以加 XML、Excel 等新格式,只需要实现 IDataReader 接口
可复用性 Logger 模块可以放在任何项目里使用

最后,实际项目中的"黄金法则":

  1. 先写能跑的代码,不要一开始就想太多
  2. 重构时再考虑这些:看到代码重复了,就抽函数;看到 if-else 多了,就用 map
  3. 不要过度设计:如果只有 2-3 种类型,用 if-else 完全没问题
  4. 当类型超过 5 个时,再考虑用多态或 map

记住:好的代码是"改"出来的,不是"写"出来的。

相关推荐
sibylyue1 小时前
工作流表单和流程设计前端
开发语言·javascript·开源
一个游离的指针1 小时前
JS中的对象的相关概念
开发语言·javascript·原型模式
jaysee-sjc2 小时前
【JavaWeb】Tlias智能学习辅助系统|后端Web实战(登录认证)
java·开发语言·前端·学习·mybatis
淡海水2 小时前
C#与常用数据结构源码剖析-全篇导览
开发语言·数据结构·unity·面试·职场和发展·c#
问天_观心2 小时前
python之uv库的学习
开发语言·python
郑州光合科技余经理2 小时前
餐饮预定系统架构拆解:订单链路、权限组织与私有化源码交付
java·开发语言·前端·数据库·人工智能·系统架构·php
在世修行2 小时前
从零打造 C# 工业视觉检测系统(十五):项目打包部署与进阶展望
开发语言·c#·视觉检测
Nebula_g2 小时前
JavaSE基础语法:特殊类(特殊情景下的设计模式)
java·开发语言·设计模式
不正经学生3 小时前
C语言大小端字节序:内存里字节的排列顺序
c语言·开发语言·arm开发·c++·算法·c#