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

张开发
2026/4/10 6:11:20 15 分钟阅读

分享文章

当C++遇上提示词工程:我用大模型重构了团队的代码审查
当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虽然啰嗦一点但其实也就是个socketJSON的事。核心模块PromptBuilder整个工具里最核心的模块不是HTTP客户端不是JSON解析器而是PromptBuilder——负责把原始的代码diff变成一段高质量的提示词。这个模块我前后重写了三版每一版的审查效果都有质的提升。V1裸提示命中率约30%最早我就是把diff原文拼上一句请审查以下代码变更直接发给API。结果可想而知回来的东西要么太泛泛要么完全跑偏。比如你改了一个网络模块的超时逻辑它给你说建议添加单元测试——没错但没用。V2规则注入命中率约65%第二版我开始认真设计提示词结构。核心改动是加了三样东西角色设定、审查清单、输出格式约束。V3Few-shot 思维链命中率约85%第三版是质的飞跃。我加入了真实的审查案例作为Few-shot示例并且要求模型先分析代码意图再逐条检查最后给出结论——这就是所谓的思维链。下面是最终版的PromptBuilder实现promptbuilder.h#ifndefPROMPT_BUILDER_H#definePROMPT_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);#includestring#includevector/// brief 审查规则条目structReviewRule{std::string category;// 规则分类如内存安全std::string description;// 具体检查内容std::string severity;// 严重等级: error / warning / info};/// brief Few-shot示例条目structFewShotExample{std::string codeSnippet;// 有问题的代码片段std::string reviewOutput;// 期望的审查输出};classPromptBuilder{public:/// brief 设置系统角色描述voidsetSystemRole(conststd::stringrole);// 定义模型扮演的角色/// brief 添加审查规则voidaddRule(constReviewRulerule);// 注入一条检查规则/// brief 批量加载规则文件boolloadRulesFromFile(conststd::stringpath);// 从JSON文件加载规则集/// brief 添加Few-shot示例voidaddFewShotExample(constFewShotExampleexample);// 注入一个审查范例/// brief 设置项目编码规范摘要voidsetCodingStandard(conststd::stringstandard);// 注入团队编码规范/// brief 构建最终提示词std::stringbuild(conststd::stringdiffContent)const;// 组装完整prompt/// brief 构建系统消息用于Chat API的system字段std::stringbuildSystemMessage()const;// 生成system角色消息private:std::string systemRole_;// 角色定义std::string codingStandard_;// 编码规范摘要std::vectorReviewRulerules_;// 审查规则列表std::vectorFewShotExampleexamples_;// Few-shot示例列表/// brief 格式化规则为文本std::stringformatRules()const;// 将规则列表拼接为文本/// brief 格式化Few-shot示例std::stringformatExamples()const;// 将示例拼接为文本/// brief 构建思维链引导语std::stringbuildChainOfThought()const;// 生成CoT推理引导};#endif// PROMPT_BUILDER_Hpromptbuilder.cpp#includepromptbuilder.h#includefstream#includesstreamvoidPromptBuilder::setSystemRole(conststd::stringrole){systemRole_role;// 存储角色描述}voidPromptBuilder::addRule(constReviewRulerule){rules_.push_back(rule);// 追加一条规则}boolPromptBuilder::loadRulesFromFile(conststd::stringpath){std::ifstreamfile(path);// 打开规则文件if(!file.is_open())// 文件不存在则返回失败returnfalse;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 sepline.find(:);// 查找键值分隔符if(sepstd::string::npos)// 格式不合法则跳过continue;std::string keyline.substr(0,sep);// 提取键名std::string valline.substr(sep1);// 提取值if(keycategory)// 解析分类字段currentRule.categoryval;elseif(keydescription)// 解析描述字段currentRule.descriptionval;elseif(keyseverity)// 解析严重度字段currentRule.severityval;}if(!currentRule.category.empty())// 处理文件末尾的最后一条rules_.push_back(currentRule);returntrue;// 加载完成}voidPromptBuilder::addFewShotExample(constFewShotExampleexample){examples_.push_back(example);// 追加一个范例}voidPromptBuilder::setCodingStandard(conststd::stringstandard){codingStandard_standard;// 存储编码规范文本}std::stringPromptBuilder::formatRules()const{std::ostringstream oss;// 拼接缓冲区oss## Review Checklist\n;// 清单标题for(size_t i0;irules_.size();i)// 遍历所有规则{constautorrules_[i];// 当前规则引用ossi1. // 序号[r.severity] // 严重等级标签r.category: // 分类r.description\n;// 具体描述}returnoss.str();// 返回格式化文本}std::stringPromptBuilder::formatExamples()const{if(examples_.empty())// 无示例则返回空return;std::ostringstream oss;// 拼接缓冲区oss## Examples of Good Reviews\n\n;// 示例区标题for(size_t i0;iexamples_.size();i)// 遍历所有示例{constautoexexamples_[i];// 当前示例引用oss### Example i1\n// 示例编号Code:\ncpp\n// 代码块开始ex.codeSnippet// 问题代码\n\n// 代码块结束Review:\n// 审查结果ex.reviewOutput\n\n;// 期望输出}returnoss.str();// 返回格式化文本}std::stringPromptBuilder::buildChainOfThought()const{return// 思维链引导模板## Analysis Steps\nPlease follow these steps:\n1. 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::stringPromptBuilder::buildSystemMessage()const{std::ostringstream oss;// 拼接系统消息ossYou are systemRole_.\n\n;// 角色设定if(!codingStandard_.empty())// 如果有编码规范oss## Project Coding Standard\ncodingStandard_\n\n;// 注入规范ossformatRules()\n;// 注入审查规则ossformatExamples();// 注入Few-shot示例ossbuildChainOfThought();// 注入思维链引导returnoss.str();// 返回完整系统消息}std::stringPromptBuilder::build(conststd::stringdiffContent)const{std::ostringstream oss;// 拼接用户消息ossPlease 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;returnoss.str();// 返回完整用户消息}使用示例#includepromptbuilder.h#includeiostreamintmain(){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::vectorchar or std::unique_ptrchar[] for automatic cleanup.});// 构建提示词std::string systemMsgbuilder.buildSystemMessage();// 生成系统消息std::string userMsgbuilder.build(diffContent);// 生成用户消息std::cout System Message \n// 输出系统消息systemMsg\n\n User Message \n// 输出用户消息userMsgstd::endl;return0;}提示词的三个关键技巧做了三版迭代之后我总结出三条对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示例是在定义干成什么样思维链是在定义按什么步骤干。这套思路不局限于代码审查。日志分析、故障诊断、技术文档生成——凡是有明确评判标准、有固定输出格式、有历史案例可参考的任务都可以用类似的方法落地。大模型的能力上限很高但下限也可以很低。区别在于你喂给它什么样的指令。这件事值得每个开发者花时间去琢磨。本文为个人项目实践分享代码经过简化实际生产版本包含更多异常处理和边界检查。

更多文章