首页 > 编程语言 >C++错误信息收集器实现

C++错误信息收集器实现

来源:互联网 2026-07-29 17:27:23

针对大型C++项目错误信息处理中的信息碎片化、重复记录、特殊字符干扰及缺乏分类等问题,设计并实现了一个轻量级ErrorCollector类。采用并行向量存储错误码与消息,通过erase-remove惯用法清洗特殊字符,利用线性查找去重,并支持按类型和错误码分类查询,有效提升了错误收集的规范性和可维护性。

1. 背景

在大型C++项目的开发过程中,错误处理是一个不容忽视的核心环节。年初接手了一个工程,几个月改下来,发现里面的错误信息处理相当凌乱——为了日后维护方便,必须来一次必要重构。缺乏统一错误收集机制会带来哪些困扰?看看下面这些问题就知道了:

C++错误信息收集器实现

长期稳定更新的攒劲资源: >>>点此立即查看<<<

  • 信息碎片化:不同模块各自定义自己的错误格式,日志散落在不同文件里,排查问题时只能手动去关联。
  • 重复记录:同一个错误可能被多个层级重复记录,日志很快就膨胀起来。
  • 特殊字符干扰:错误消息里夹杂的换行符、制表符破坏日志格式,后续分析根本没法做。
  • 缺乏分类:无法区分致命错误、警告和普通提示信息,现场一片混乱。

要解决这些问题,最直接的办法就是设计并实现一个轻量级的ErrorCollector类。下面就从需求分析、设计思路、实现细节到测试验证,完整走一遍这个组件的开发过程。

PS:这几年基本都会因为同事升职、转岗等原因接手不同性质的代码工程,但还没能力向他人移交工程或事务。从技术角度看,能写不同类别的代码是好事;但从个人成长角度看,这可不是什么好兆头,尤其是年龄越来越大、头发越来越白的尴尬阶段。

2. 设计思路

2.1 核心需求

梳理一下需求,核心功能点其实很明确:

  1. 去重存储:相同的错误消息只保留一份,避免冗余。
  2. 分类标记:每条消息关联一个错误码(0表示普通信息,非0表示错误)。
  3. 字符清洗:自动移除tnr这些特殊字符。
  4. 灵活查询:支持按类型(全部/仅错误)和按错误码过滤。
  5. 空值安全:对空字符串和空指针要有防护,不能崩。
  6. 简单易用:接口直观,而且只有头文件,降低使用成本。

2.2 数据结构设计

采用两个并行的std::vector分别存储错误码和消息文本:

std::vector m_code;          // 错误码列表
std::vector m_list;  // 消息列表(与m_code一一对应)

这么设计的优势在于:

  • 索引对应:通过相同下标关联错误码和消息,不需要额外的映射结构(注意索引要≥0)。
  • 顺序保留:保持添加顺序,便于追溯问题发生的时间线。
  • 内存友好:相比std::mapstd::unordered_map,内存开销更小。

去重机制通过std::findm_list中线性查找实现。虽然时间复杂度是O(n),但考虑到错误收集场景下消息数量通常有限(一般不超过几十条),这种实现足够高效,代码也简洁。

2.3 接口设计原则

  • 重载友好:同时支持std::stringconst char*参数,适应不同使用场景。
  • 默认参数code参数默认为0,简化普通信息记录的调用。
  • 防御性编程:在函数入口进行参数校验,提前返回避免无效操作。
  • const正确性:查询接口标记为const,确保不修改对象状态。

3. 实践

3.1 源码

ErrorCollector.h文件代码如下:

#include  
#include 
#include 
#include 

// 错误信息、过程信息记录器
class ErrorCollector {
public:
    // 构造函数,初始化变量为空
    ErrorCollector() {
        m_code.clear();
        m_list.clear();
    }
    
    // 析构函数(可选)
    ~ErrorCollector() {
        m_code.clear();
        m_list.clear();
    }

    void AddInfo(const std::string& msg1, int code = 0) {
        // 判断字符串是否为空
        if (msg1.empty()) {
            return;
        }
        std::string msg = msg1;

        msg.erase(std::remove_if(msg.begin(), msg.end(),
        [](char c) { return c == 't' || c == 'n' || c == 'r'; }), 
        msg.end());

        // 删除特殊字符后再次判断是否为空
        if (msg.empty()) {
            return;
        }
        if (std::find(m_list.begin(), m_list.end(), msg) == m_list.end()) {
            m_code.push_back(code);
            m_list.push_back(msg);
        }
    }
    void AddInfo(const char* msg1, int code = 0) {
        // 判断指针是否为空,以及字符串是否为空
        if (msg1 == nullptr || msg1[0] == '') {
            return;
        }
        std::string msg = msg1;
        AddInfo(msg, code);
    }

    // 返回竖线分隔的字符串,格式:错误1 | 错误2 | 错误3
    // all 表示所有的信息,如只返回错误,设置为false
    std::string GetInfos(bool all = true) const {
        std::string result;
        int cnt = 0;
        for (size_t i = 0; i < m_list.size(); i++) {
            if (!all && m_code[i] == 0) {
                continue;
            }
            if (cnt > 0) {
                result += " | ";
            }
            cnt++;
            result += m_list[i];
        }
        
        if (!result.empty() && result.back() == '|') {
            result.pop_back();
        }
        return result;
    }
    // 同上,指定code
    std::string GetInfoByCode(int code) const {
        std::string result;
        int cnt = 0;
        for (size_t i = 0; i < m_list.size(); i++) {
            if (m_code[i] != code) {
                continue;
            }
            if (cnt > 0) {
                result += " | ";
            }
            cnt++;
            result += m_list[i];
        }
        
        if (!result.empty() && result.back() == '|') {
            result.pop_back();
        }
        return result;
    }
    std::string GetCodes() const {
        std::string result;
        int cnt = 0;
        for (size_t i = 0; i < m_code.size(); i++) {
            if (m_code[i] == 0) {
                continue;
            }
            if (cnt > 0) {
                result += "|"; // 不要空格紧凑一些
            }
            cnt++;
            result += std::to_string(m_code[i]);
        }
        if (!result.empty() && result.back() == '|') {
            result.pop_back();
        }
        return result;
    }
    // 可指定忽略的code
    std::string GetCodes(int skipCode) const {
        std::string result;
        int cnt = 0;
        for (size_t i = 0; i < m_code.size(); i++) {
            if (m_code[i] == 0 || m_code[i] == skipCode) {
                continue;
            }
            if (cnt > 0) {
                result += "|"; // 不要空格紧凑一些
            }
            cnt++;
            result += std::to_string(m_code[i]);
        }
        if (!result.empty() && result.back() == '|') {
            result.pop_back();
        }
        return result;
    }

    bool Empty() {return m_list.empty();}

private:
    std::vector m_code;
    std::vector m_list;
};

3.2 关键实现解析

特殊字符清洗

msg.erase(std::remove_if(msg.begin(), msg.end(),
    [](char c) { return c == 't' || c == 'n' || c == 'r'; }), 
    msg.end());

这里用了经典的"erase-remove"惯用法,高效地移除了三种常见的格式干扰字符。清洗后再判断消息是否为空,确保不会存储仅由特殊字符组成的无效消息。

去重逻辑

if (std::find(m_list.begin(), m_list.end(), msg) == m_list.end()) {
    m_code.push_back(code);
    m_list.push_back(msg);
}

值得注意的地方是:去重基于消息内容,而不是消息+错误码的组合。这意味着如果同一个错误消息先后以不同的错误码添加,只有第一次会被保留,后续的添加会被忽略。这种设计在某些场景下可能丢失错误码信息,但换来的是消息列表的简洁性。实际使用中,我们约定错误消息应该包含足够的上下文信息,这样重复消息确实不需要再次记录。

输出格式控制

if (cnt > 0) {
    result += " | ";
}
cnt++;
result += m_list[i];

通过计数器控制分隔符的插入位置,避免在字符串开头或结尾出现多余的分隔符。GetCodes方法中则使用更紧凑的"|"(无空格)作为分隔符,方便后续解析。

3.3 使用示例

ErrorCollector collector;

// 记录普通信息
collector.AddInfo("开始数据迁移");

// 记录错误
collector.AddInfo("连接数据库失败", 1001);
collector.AddInfo("读取配置文件异常", 1002);

// 获取所有信息(包含提示和错误)
std::string all = collector.GetInfos(true);
// 输出: "开始数据迁移 | 连接数据库失败 | 读取配置文件异常"

// 仅获取错误信息
std::string errors = collector.GetInfos(false);
// 输出: "连接数据库失败 | 读取配置文件异常"

// 获取错误码列表
std::string codes = collector.GetCodes();
// 输出: "1001|1002"

// 按错误码查询
std::string info = collector.GetInfoByCode(1001);
// 输出: "连接数据库失败"

3.4 应用场景

ErrorCollector在以下场景中特别有用:

  1. API参数校验:收集所有校验失败的信息,一次性返回给调用方。
  2. 批量操作:执行多个独立操作时,收集所有异常,而不是遇到第一个错误就中断。
  3. 配置加载:加载配置文件时,记录所有格式错误或缺失的配置项。
  4. 单元测试:收集测试过程中的断言失败信息。

4. 测试

4.1 测试代码

测试代码文件main.cpp:

#include 
#include "ErrorCollector.h"

void printTestResult(const std::string& testName, bool passed) {
    std::cout << (passed  "[PASS]" : "[FAIL]") << " " << testName << std::endl;
}

int main() {
    ErrorCollector collector;
    
    // 测试1:基本添加和获取
    std::cout << "n=== 测试1:基本功能 ===" << std::endl;
    collector.AddInfo("数据库连接失败", 1001);
    collector.AddInfo("配置文件缺失", 1002);
    collector.AddInfo("用户认证超时", 0);  // code为0表示提示信息
    
    std::string infos = collector.GetInfos(true);
    std::cout << "所有信息: " << infos << std::endl;
    std::cout << "仅错误: " << collector.GetInfos(false) << std::endl;
    std::cout << "错误码: " << collector.GetCodes() << std::endl;
    printTestResult("基本功能", infos == "数据库连接失败 | 配置文件缺失 | 用户认证超时");
    
    // 测试2:去重功能
    std::cout << "n=== 测试2:去重功能 ===" << std::endl;
    ErrorCollector collector2;
    collector2.AddInfo("重复错误", 100);
    collector2.AddInfo("重复错误", 200);  // 相同消息,不同code
    collector2.AddInfo("唯一错误", 300);
    
    std::string result = collector2.GetInfos(true);
    std::cout << "去重后结果: " << result << std::endl;
    std::cout << "错误码: " << collector2.GetCodes() << std::endl;
    printTestResult("去重功能", result == "重复错误 | 唯一错误" && collector2.GetCodes() == "100|300");
    
    // 测试3:空字符串和特殊字符
    std::cout << "n=== 测试3:空值和特殊字符过滤 ===" << std::endl;
    ErrorCollector collector3;
    collector3.AddInfo("", 100);
    collector3.AddInfo("tnr", 200);
    collector3.AddInfo("有效消息", 300);
    collector3.AddInfo(nullptr, 400);
    
    std::string result3 = collector3.GetInfos(true);
    std::cout << "过滤后结果: " << result3 << std::endl;
    printTestResult("空值过滤", result3 == "有效消息");
    printTestResult("空状态检测", !collector3.Empty());
    
    // 测试4:特殊字符清理(制表符、换行符)
    std::cout << "n=== 测试4:特殊字符清理 ===" << std::endl;
    ErrorCollector collector4;
    collector4.AddInfo("消息t包含t制表符", 100);
    collector4.AddInfo("第一行n第二行rn", 200);
    
    std::string result4 = collector4.GetInfos(true);
    std::cout << "清理后结果: " << result4 << std::endl;
    printTestResult("制表符清理", result4 == "消息包含制表符 | 第一行第二行");
    
    // 测试5:按错误码过滤
    std::cout << "n=== 测试5:按错误码获取 ===" << std::endl;
    ErrorCollector collector5;
    collector5.AddInfo("错误A", 100);
    collector5.AddInfo("提示B", 0);
    collector5.AddInfo("错误C", 200);
    collector5.AddInfo("错误D", 300);
    
    std::cout << "错误码100的信息: " << collector5.GetInfoByCode(100) << std::endl;
    std::cout << "跳过错误码200: " << collector5.GetCodes(200) << std::endl;
    printTestResult("按码获取", collector5.GetInfoByCode(100) == "错误A");
    printTestResult("跳过码获取", collector5.GetCodes(200) == "100|300");
    
    // 测试6:格式验证
    std::cout << "n=== 测试6:输出格式验证 ===" << std::endl;
    ErrorCollector collector6;
    collector6.AddInfo("A", 1);
    collector6.AddInfo("B", 2);
    collector6.AddInfo("C", 3);
    
    std::string result6 = collector6.GetInfos(true);
    std::cout << "格式结果: " << result6 << std::endl;
    bool formatOk = (result6 == "A | B | C") && 
                    (result6.back() != '|') && 
                    (result6.front() != '|');
    printTestResult("格式正确", formatOk);
    
    // 测试7:空状态检测
    std::cout << "n=== 测试7:空状态检测 ===" << std::endl;
    ErrorCollector collector7;
    std::cout << "初始Empty: " << (collector7.Empty()  "true" : "false") << std::endl;
    collector7.AddInfo("测试", 100);
    std::cout << "添加后Empty: " << (collector7.Empty()  "true" : "false") << std::endl;
    printTestResult("空状态检测", collector7.Empty() == false);
    
    // 测试8:大量数据性能
    std::cout << "n=== 测试8:大量数据性能 ===" << std::endl;
    ErrorCollector collector8;
    for (int i = 0; i < 1000; i++) {
        collector8.AddInfo("错误_" + std::to_string(i), i);
    }
    std::string result8 = collector8.GetInfos(true);
    bool hasError500 = result8.find("错误_500") != std::string::npos;
    std::cout << "包含第500条: " << (hasError500  "是" : "否") << std::endl;
    printTestResult("大量数据处理", hasError500);
    
    std::cout << "n=== 所有测试完成 ===" << std::endl;
    return 0;
}

编译命令:

g++ -std=c++11  main.cpp

执行输出结果:

# ./a.out

=== 测试1:基本功能 ===
所有信息: 数据库连接失败 | 配置文件缺失 | 用户认证超时
仅错误: 数据库连接失败 | 配置文件缺失
错误码: 1001|1002
[PASS] 基本功能

=== 测试2:去重功能 ===
去重后结果: 重复错误 | 唯一错误
错误码: 100|300
[PASS] 去重功能

=== 测试3:空值和特殊字符过滤 ===
过滤后结果: 有效消息
[PASS] 空值过滤
[PASS] 空状态检测

=== 测试4:特殊字符清理 ===
清理后结果: 消息包含制表符 | 第一行第二行
[PASS] 制表符清理

=== 测试5:按错误码获取 ===
错误码100的信息: 错误A
跳过错误码200: 100|300
[PASS] 按码获取
[PASS] 跳过码获取

=== 测试6:输出格式验证 ===
格式结果: A | B | C
[PASS] 格式正确

=== 测试7:空状态检测 ===
初始Empty: true
添加后Empty: false
[PASS] 空状态检测

=== 测试8:大量数据性能 ===
包含第500条: 是
[PASS] 大量数据处理

=== 所有测试完成 ===

4.2 关键测试用例

用例1:基本功能验证

collector.AddInfo("数据库连接失败", 1001);
collector.AddInfo("配置文件缺失", 1002);
collector.AddInfo("用户认证超时", 0);

用例2:去重验证

    collector2.AddInfo("重复错误", 100);
    collector2.AddInfo("重复错误", 200);  // 相同消息,不同code
    collector2.AddInfo("唯一错误", 300);

用例3:特殊字符清理

    ErrorCollector collector4;
    collector4.AddInfo("消息t包含t制表符", 100);
    collector4.AddInfo("第一行n第二行rn", 200);

4.3 测试结论

所有测试用例全部通过,验证了实现的正确性和健壮性。

测试项预期结果实际结果状态
基本添加和获取正确存储和检索通过
消息去重相同消息只保留一个通过
空值过滤空字符串被忽略通过
特殊字符清理移除tnr通过
按错误码获取正确过滤通过
跳过指定错误码正确排除通过
输出格式竖线分隔,无多余符号通过
空状态检测状态正确通过
性能测试1000条数据正常处理通过

5. 其它说明

ErrorCollector虽然是一个简单的工具类,但在实际项目中发挥着重要作用。以下几点值得关注,也是未来可能改进的方向:

  1. 其它信息收集ErrorCollector虽然名为错误收集器,但通过创建多个副本,也可以用来收集其他类型的信息。另外,可以根据需要扩展。
  2. 线程安全:在多线程环境下,需要添加互斥锁保护。
  3. 容量限制:防止错误消息无限增长,可以设置最大容量。
  4. 持久化:支持将错误信息序列化到文件或数据库。

6. 小结

错误收集看似简单,但设计一个好用、健壮的错误收集器,需要对使用场景有深入理解。ErrorCollector的实践说明,即使是小型工具类,只要设计得当,也能在项目中发挥"四两拨千斤"的效果。

在软件开发中,我们常常追求复杂而全面的解决方案,但有时候,一个简洁、专注的工具可能更符合"够用就好"的工程哲学。正如这个错误收集器所展示的:在正确的抽象层次上,解决正确的问题,就是好的设计

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备2026025700号-3 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。