当C++遇上提示词工程:我用大模型重构了团队的代码审查

三个月前,我们组每周的Code Review会议要开两个小时,现在只需要四十分钟。变化的起点不是换了流程,而是我花两个周末写了一个500行的C++小工具。

背景:代码审查的老痛点

我们组做的是嵌入式中间件,C++代码库大概二十万行。团队八个人,每周合入的MR少说也有三四十个。

代码审查这件事的问题,不在于大家不重视——恰恰相反,大家都知道重要,但实际执行起来总是打折扣。核心原因就两个字:没空

每个人手上都有排期压力,让你花半小时仔细看别人的代码,嘴上说没问题,实际上经常是扫一眼就LGTM了。结果就是:看起来每个MR都有审查记录,但内存泄漏、线程安全这类深层问题,经常是上线之后才暴露。

去年底有一次线上事故,根因是一个std::shared_ptr的循环引用导致内存持续增长,Review的时候三个人都没看出来。事后复盘的时候,组长说了一句话:“咱们的Code Review到底是在审代码,还是在走形式?”

这句话刺激了我。我开始想,能不能用大模型来做一层自动化的预审查——不是替代人工审查,而是在人工Review之前,先用AI过一遍,把明显的问题标出来。

整体思路

想法其实很简单:Git Hook触发,提取代码diff,组装提示词,调LLM的API,拿到审查意见,生成报告。

但实际做的时候发现,这件事的难点完全不在"调API",而在"怎么写提示词"。同一段有问题的代码,提示词写得好,模型能精确定位到具体行号并给出修复建议;提示词写得差,模型只会给你"建议添加错误处理"这种废话。

在这里插入图片描述

整个工具我用C++写的,没用Python,原因很务实——我们的CI环境是纯C++工具链,加一个Python运行时代价太大。C++调HTTP API虽然啰嗦一点,但其实也就是个socket+JSON的事。

核心模块:PromptBuilder

整个工具里最核心的模块不是HTTP客户端,不是JSON解析器,而是PromptBuilder——负责把原始的代码diff变成一段高质量的提示词。

这个模块我前后重写了三版,每一版的审查效果都有质的提升。

在这里插入图片描述

V1:裸提示(命中率约30%)

最早我就是把diff原文拼上一句"请审查以下代码变更"直接发给API。结果可想而知,回来的东西要么太泛泛,要么完全跑偏。比如你改了一个网络模块的超时逻辑,它给你说"建议添加单元测试"——没错,但没用。

V2:规则注入(命中率约65%)

第二版我开始认真设计提示词结构。核心改动是加了三样东西:角色设定、审查清单、输出格式约束。

V3:Few-shot + 思维链(命中率约85%)

第三版是质的飞跃。我加入了真实的审查案例作为Few-shot示例,并且要求模型先分析代码意图,再逐条检查,最后给出结论——这就是所谓的"思维链"。

下面是最终版的PromptBuilder实现:

promptbuilder.h

#ifndef PROMPT_BUILDER_H
#define PROMPT_BUILDER_H

/// @file promptbuilder.h
/// @brief 提示词构建器 v3.0
/// @details 支持角色设定、规则注入、Few-shot示例、思维链引导
/// @usage
///   PromptBuilder builder;
///   builder.setSystemRole("Senior C++ code reviewer");
///   builder.addRule("检查RAII合规性");
///   builder.addFewShotExample(badCode, reviewResult);
///   auto prompt = builder.build(diffContent);

#include <string>
#include <vector>

/// @brief 审查规则条目
struct ReviewRule
{
    std::string category;                          // 规则分类(如"内存安全")
    std::string description;                       // 具体检查内容
    std::string severity;                          // 严重等级: error / warning / info
};

/// @brief Few-shot示例条目
struct FewShotExample
{
    std::string codeSnippet;                       // 有问题的代码片段
    std::string reviewOutput;                      // 期望的审查输出
};

class PromptBuilder
{
public:
    /// @brief 设置系统角色描述
    void setSystemRole(const std::string& role);   // 定义模型扮演的角色

    /// @brief 添加审查规则
    void addRule(const ReviewRule& rule);           // 注入一条检查规则

    /// @brief 批量加载规则文件
    bool loadRulesFromFile(const std::string& path); // 从JSON文件加载规则集

    /// @brief 添加Few-shot示例
    void addFewShotExample(const FewShotExample& example); // 注入一个审查范例

    /// @brief 设置项目编码规范摘要
    void setCodingStandard(const std::string& standard);   // 注入团队编码规范

    /// @brief 构建最终提示词
    std::string build(const std::string& diffContent) const; // 组装完整prompt

    /// @brief 构建系统消息(用于Chat API的system字段)
    std::string buildSystemMessage() const;        // 生成system角色消息

private:
    std::string systemRole_;                       // 角色定义
    std::string codingStandard_;                   // 编码规范摘要
    std::vector<ReviewRule> rules_;                 // 审查规则列表
    std::vector<FewShotExample> examples_;          // Few-shot示例列表

    /// @brief 格式化规则为文本
    std::string formatRules() const;               // 将规则列表拼接为文本

    /// @brief 格式化Few-shot示例
    std::string formatExamples() const;            // 将示例拼接为文本

    /// @brief 构建思维链引导语
    std::string buildChainOfThought() const;       // 生成CoT推理引导
};

#endif // PROMPT_BUILDER_H

promptbuilder.cpp

#include "promptbuilder.h"

#include <fstream>
#include <sstream>

void PromptBuilder::setSystemRole(const std::string& role)
{
    systemRole_ = role;                            // 存储角色描述
}

void PromptBuilder::addRule(const ReviewRule& rule)
{
    rules_.push_back(rule);                        // 追加一条规则
}

bool PromptBuilder::loadRulesFromFile(const std::string& path)
{
    std::ifstream file(path);                      // 打开规则文件
    if (!file.is_open())                           // 文件不存在则返回失败
        return false;

    std::string line;                              // 逐行读取缓冲
    ReviewRule currentRule;                         // 当前解析的规则

    while (std::getline(file, line))               // 逐行遍历
    {
        if (line.empty())                          // 空行表示一条规则结束
        {
            if (!currentRule.category.empty())      // 确保规则有效
            {
                rules_.push_back(currentRule);      // 保存已解析的规则
                currentRule = {};                   // 重置为空规则
            }
            continue;
        }

        size_t sep = line.find(':');                // 查找键值分隔符
        if (sep == std::string::npos)              // 格式不合法则跳过
            continue;

        std::string key = line.substr(0, sep);     // 提取键名
        std::string val = line.substr(sep + 1);    // 提取值

        if (key == "category")                     // 解析分类字段
            currentRule.category = val;
        else if (key == "description")             // 解析描述字段
            currentRule.description = val;
        else if (key == "severity")                // 解析严重度字段
            currentRule.severity = val;
    }

    if (!currentRule.category.empty())             // 处理文件末尾的最后一条
        rules_.push_back(currentRule);

    return true;                                   // 加载完成
}

void PromptBuilder::addFewShotExample(const FewShotExample& example)
{
    examples_.push_back(example);                  // 追加一个范例
}

void PromptBuilder::setCodingStandard(const std::string& standard)
{
    codingStandard_ = standard;                    // 存储编码规范文本
}

std::string PromptBuilder::formatRules() const
{
    std::ostringstream oss;                        // 拼接缓冲区
    oss << "## Review Checklist\n";                // 清单标题

    for (size_t i = 0; i < rules_.size(); ++i)     // 遍历所有规则
    {
        const auto& r = rules_[i];                 // 当前规则引用
        oss << i + 1 << ". "                       // 序号
            << "[" << r.severity << "] "           // 严重等级标签
            << r.category << ": "                  // 分类
            << r.description << "\n";              // 具体描述
    }

    return oss.str();                              // 返回格式化文本
}

std::string PromptBuilder::formatExamples() const
{
    if (examples_.empty())                         // 无示例则返回空
        return "";

    std::ostringstream oss;                        // 拼接缓冲区
    oss << "## Examples of Good Reviews\n\n";      // 示例区标题

    for (size_t i = 0; i < examples_.size(); ++i)  // 遍历所有示例
    {
        const auto& ex = examples_[i];             // 当前示例引用
        oss << "### Example " << i + 1 << "\n"    // 示例编号
            << "Code:\n```cpp\n"                   // 代码块开始
            << ex.codeSnippet                      // 问题代码
            << "\n```\n"                           // 代码块结束
            << "Review:\n"                         // 审查结果
            << ex.reviewOutput << "\n\n";          // 期望输出
    }

    return oss.str();                              // 返回格式化文本
}

std::string PromptBuilder::buildChainOfThought() const
{
    return                                         // 思维链引导模板
        "## Analysis Steps\n"
        "Please follow these steps:\n"
        "1. Understand the INTENT of this change\n"    // 步骤1: 理解意图
        "2. Check each rule in the checklist\n"        // 步骤2: 逐条检查
        "3. For each issue found, provide:\n"          // 步骤3: 问题输出格式
        "   - Line number\n"                           // 行号
        "   - Severity (error/warning/info)\n"         // 严重度
        "   - Problem description\n"                   // 问题描述
        "   - Suggested fix with code\n"               // 修复建议
        "4. If no issues found, explicitly state LGTM\n"; // 无问题则LGTM
}

std::string PromptBuilder::buildSystemMessage() const
{
    std::ostringstream oss;                        // 拼接系统消息

    oss << "You are " << systemRole_ << ".\n\n";  // 角色设定

    if (!codingStandard_.empty())                  // 如果有编码规范
        oss << "## Project Coding Standard\n"
            << codingStandard_ << "\n\n";          // 注入规范

    oss << formatRules() << "\n";                  // 注入审查规则
    oss << formatExamples();                       // 注入Few-shot示例
    oss << buildChainOfThought();                  // 注入思维链引导

    return oss.str();                              // 返回完整系统消息
}

std::string PromptBuilder::build(const std::string& diffContent) const
{
    std::ostringstream oss;                        // 拼接用户消息

    oss << "Please review the following C++ code change:\n\n" // 审查指令
        << "```diff\n"                             // diff代码块开始
        << diffContent                             // 实际的代码变更
        << "\n```\n\n"                             // diff代码块结束
        << "Provide your review following "        // 引导按格式输出
        << "the analysis steps above.\n";

    return oss.str();                              // 返回完整用户消息
}

使用示例:

#include "promptbuilder.h"
#include <iostream>

int main()
{
    PromptBuilder builder;                         // 创建构建器实例

    // 设置审查角色
    builder.setSystemRole(                         // 定义角色身份
        "a senior C++ developer with 10+ years experience "
        "specializing in memory safety and concurrency"
    );

    // 注入编码规范
    builder.setCodingStandard(                     // 注入团队规范摘要
        "- Use RAII for all resource management\n"
        "- Prefer const reference over pointer\n"
        "- All public methods must be thread-safe"
    );

    // 添加审查规则
    builder.addRule({                              // 规则1: 内存安全
        "Memory Safety",
        "Check for raw new/delete, prefer smart pointers",
        "error"
    });
    builder.addRule({                              // 规则2: 线程安全
        "Thread Safety",
        "Check shared state access without mutex",
        "error"
    });
    builder.addRule({                              // 规则3: 异常安全
        "Exception Safety",
        "Check for resource leaks in exception paths",
        "warning"
    });

    // 添加Few-shot示例
    builder.addFewShotExample({                    // 注入一个审查范例
        "void process(Data* d) {\n"                // 问题代码
        "    auto* buf = new char[1024];\n"
        "    d->parse(buf);\n"
        "    delete[] buf;\n"
        "}",
        "[error] Line 2: Raw new/delete detected.\n"  // 期望的审查输出
        "Suggested fix: use std::vector<char> or "
        "std::unique_ptr<char[]> for automatic cleanup."
    });

    // 构建提示词
    std::string systemMsg = builder.buildSystemMessage(); // 生成系统消息
    std::string userMsg = builder.build(diffContent);     // 生成用户消息

    std::cout << "=== System Message ===\n"        // 输出系统消息
              << systemMsg << "\n\n"
              << "=== User Message ===\n"          // 输出用户消息
              << userMsg << std::endl;

    return 0;
}

提示词的三个关键技巧

做了三版迭代之后,我总结出三条对C++代码审查最有效的提示词技巧:

第一,给模型一个具体的专家人设。 不是笼统的"你是一个代码审查员",而是"你是一个有10年经验的C++开发者,专长内存安全和并发编程"。人设越具体,模型给出的建议就越贴近实际。

第二,审查规则要分严重等级。 如果你把所有问题都标成同一级别,模型会倾向于平均用力,把命名规范和内存泄漏放在同一个权重。分了error/warning/info之后,模型会优先花精力分析高严重度的问题。

第三,Few-shot示例比任何描述都管用。 与其花200字解释"什么是好的审查意见",不如直接给一个例子。我最后放了三个示例:一个内存安全的、一个线程安全的、一个性能相关的。模型会自动学习这些示例的分析深度和输出格式。

实际效果

工具上线三个月,跑了大概四百多次自动审查。数据是真的,但统计方式比较粗糙——我就是手动抽了每周的审查报告做的对比,不算严谨的A/B测试。

在这里插入图片描述

几个比较突出的变化:

内存相关问题的检出率提升最明显。shared_ptr循环引用、裸new/delete、异常路径的资源泄漏,这些是人眼最容易忽略但模型最擅长捕捉的。毕竟模型不会"扫一眼就过",它会逐行看。

审查耗时从平均45分钟降到12分钟。 这不是说人不看了——而是AI先出一份预审报告,把可疑的地方标出来,人只需要看标红的部分做二次确认。相当于从"大海捞针"变成了"验证答案"。

最意外的收获是新人上手速度变快了。 以前新人提MR,老员工要花很多时间在"教你什么是好代码"上面。现在AI的审查报告本身就是一份活的编码规范教材,新人看几次就知道团队在意什么。

踩过的坑

说几个实际开发中踩的坑,给想做类似事情的人省点时间。

Diff太长的处理。 大模型的上下文窗口是有限的,一个大MR改了几十个文件、上千行diff,直接丢进去会被截断。我的做法是按函数级别切分diff,每个函数单独发一次请求,最后合并结果。代价是API调用次数增加,成本上来了,但准确率也上来了。

JSON输出的稳定性问题。 即使你在提示词里明确要求"输出JSON格式",模型偶尔还是会在前后加一些解释性文字,导致JSON解析失败。最终我的方案是用正则先提取markdown代码块里的JSON,再做解析,同时加了重试逻辑。

误报的心理成本。 这是最容易被忽视的问题。如果AI报了10个问题里有5个是误报,时间久了人就会养成"直接忽略AI建议"的习惯,比没有AI还糟糕。所以我在V3版本里大幅提高了报告的门槛——宁可漏报,不要误报。对于"不确定"的问题,标成info级别单独放在报告末尾,不打断主审查流程。

C++的模板代码几乎没法审。 这一点和我上一篇文章的结论一致。涉及到SFINAE、折叠表达式、Concepts这类高级模板技巧,模型给出的建议经常是错的。我的策略是在规则里明确标注"跳过模板元编程相关变更",不审比瞎审好。

成本核算

很多人关心调API的成本。以我们的使用量为例:每周约40个MR,每个MR平均3次API调用(按函数切分),每次调用约4K tokens输入+1K tokens输出。按Claude Sonnet的价格算下来,每月大概不到200块人民币。

对比一下:一个资深开发者每周花在Code Review上的时间,按时薪折算至少是这个成本的十倍。

写在最后

做这个工具让我体会最深的一点是:提示词工程不是"调参",是"产品设计"。

你不是在调一个黑盒的超参数,你是在设计一套人和模型的协作协议。角色设定是在定义"谁来干活",审查规则是在定义"干什么活",Few-shot示例是在定义"干成什么样",思维链是在定义"按什么步骤干"。

这套思路不局限于代码审查。日志分析、故障诊断、技术文档生成——凡是有明确评判标准、有固定输出格式、有历史案例可参考的任务,都可以用类似的方法落地。

大模型的能力上限很高,但下限也可以很低。区别在于你喂给它什么样的指令。这件事,值得每个开发者花时间去琢磨。


本文为个人项目实践分享,代码经过简化,实际生产版本包含更多异常处理和边界检查。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐