ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

AI代码评审Skill:构建证据链让合并决策更可信

2026/10/8 11:07:04 拓冰建站 浏览量
AI代码评审Skill:构建证据链让合并决策更可信 1. 当AI把代码写完之后评审环节到底卡在了哪里最近半年我身边几乎所有做开发的朋友都在用AI辅助写代码。不管是补全一个函数、生成单元测试还是把一段老代码重构掉AI确实快得离谱。但有意思的是大家聊得最多的抱怨已经从“AI写得不对”变成了另一句话“它写得挺对但我不敢合。”这个心态转变特别值得琢磨。以前AI写错代码你一眼就能看出来直接改掉或者重写就行。现在AI生成的代码往往语法正确、逻辑自洽、甚至风格还挺优雅可你就是心里没底。为什么因为你看不出它“为什么这么写”。它可能悄悄改了一个边界条件可能引入了一个你没听说过的依赖也可能把某个异常处理逻辑简化成了看起来没问题、实际在极端场景下会炸的写法。你手里只有一个git diff红红绿绿的一堆行但缺少一条能让你信服的证据链。这就是我最近一直在折腾的一个方向给代码评审这个环节做一个专门的Skill。不是那种泛泛的“AI帮你看看代码”而是让AI在评审时能拿出证据、说清楚依据、标明白风险等级最终让你敢按下那个合并按钮。关键词里的AI、代码评审、Skill、Agent、git diff其实正好串起了这条线AI负责生成Agent负责执行Skill负责把评审这件事做得有章法而git diff就是整个证据链的起点。这篇文章适合两类人看。一类是已经在用AI写代码、但评审时总觉得心里发虚的开发者另一类是想给自己团队搭一套AI辅助评审流程的技术负责人。我会把整个思路拆开讲包括为什么普通AI评审不够用、一个有证据链的评审Skill应该长什么样、具体怎么落地、以及我在实操中踩过的那些坑。不堆概念直接讲能抄作业的东西。2. 普通AI评审为什么给不了你“敢合并”的底气2.1 大多数AI评审的本质是“再生成一遍”而不是“对照检查”我试过很多种让AI帮忙看代码的方式。最常见的做法就是把git diff贴给模型然后问一句“这段改动有没有问题”。模型通常会给你一段看起来挺专业的回复比如“整体逻辑清晰建议关注空值处理”。但你仔细一想这句话放在任何一段代码上都成立它根本没有针对你这次改动的具体上下文。问题出在哪儿出在大多数AI评审的底层动作是“基于diff再生成一段评论”而不是“基于证据做一次对照检查”。它没有真正去读你改动的那个文件的完整内容没有去看被调用函数的签名没有去查这个依赖在项目里其他地方是怎么用的。它只是根据diff里的几行文字凭训练时的模式记忆生成了一段“像评审意见的文字”。这两者的差别就像一个是医生看了你的化验单说“注意休息”另一个是医生对照你的历史病历、当前用药、过敏史然后告诉你“这个指标升高是因为上周换的药建议减量”。前者听着没错但没用后者才叫证据链。2.2 缺少证据链的三个典型症状我总结了一下没有证据链的AI评审通常会有这么几个表现。第一个症状是结论没有出处。它说“这里可能有并发问题”但不告诉你它依据的是哪一行、哪个共享变量、哪个没有加锁的写操作。你没法验证也没法反驳只能凭感觉决定信不信。第二个症状是风险等级模糊。它把所有问题都平铺直叙地列出来一个拼写错误和一个潜在的资源泄漏看起来一样严重。你读完不知道哪个必须改、哪个可以放行评审的决策价值就没了。第三个症状是无法追溯到具体变更。它给的评论和git diff里的具体hunk对不上号。你看着评论还得自己回去翻diff找它说的是哪一段来回切换几次之后人就烦了最后干脆“看着差不多就合了”。这三个症状叠加起来结果就是AI评审做了但你没获得任何决策依据该不敢合还是不敢合。2.3 为什么“敢不敢合并”本质是一个信任问题说到底合并代码这个动作本质是在做一个风险接受决策。你按下合并按钮意味着你愿意为这次改动上线后的一切后果负责。人类评审之所以能让你放心是因为评审者会告诉你“我看了这块逻辑我验证过那个边界我确认过剩下的风险我评估过可以接受。”AI评审要让人敢合并就必须模拟这个“可追溯的确认过程”。它不能只说“没问题”它得说“我检查了A、B、C三个点其中A和B通过C存在一个中等风险依据是某某建议你这样处理”。这就是证据链的含义每一个结论都能追溯到具体的代码位置、具体的检查项、具体的判断依据。一个合格的评审Skill核心任务不是“找bug”而是“构建一条让人类可以快速验证的信任链”。找bug只是这条链上的一个环节。3. 一个有证据链的评审Skill内部到底在做什么3.1 从git diff出发但不止于git diff很多人以为评审的输入就是git diff。没错diff是起点但如果只给Skill看diff它能拿到的信息非常有限。一个设计合理的评审Skill在拿到diff之后应该主动去扩展上下文。具体来说它会做这么几件事。第一解析diff识别出这次改动涉及哪些文件、哪些函数、哪些代码块。第二对于每个被修改的函数去读取该函数的完整定义而不只是diff里显示的那几行。第三查找这个函数在项目里被哪些地方调用评估改动的影响范围。第四如果改动引入了新的依赖或新的API调用去检查项目里是否已经有类似用法保持一致性。这一步的价值在于它把“孤立的几行改动”还原成了“项目上下文中的一次变更”。只有在这个层面上评审结论才有意义。我实测下来光是加上“读取被改函数完整定义”这一个动作评审意见的准确率就有明显提升因为它能看到diff窗口之外的前置条件判断。3.2 把评审拆成可验证的检查项而不是笼统的“看看有没有问题”证据链的另一个关键是把评审动作结构化。笼统地问“有没有问题”模型只能给你笼统的回答。但如果你把评审拆成一组明确的检查项每个检查项都有明确的判断标准和输出格式情况就完全不同了。我在自己的Skill里把评审拆成了这么几类检查项正确性检查改动的逻辑是否和意图一致边界条件是否覆盖返回值是否在所有分支都有处理。一致性检查新代码是否遵循了项目现有的命名规范、错误处理模式、日志格式。影响面检查被修改的函数/模块的调用方是否受影响接口签名变化是否同步更新了调用点。安全性检查是否有硬编码的敏感信息是否有未经验证的输入直接进入关键路径。可测试性检查新增逻辑是否可被现有测试框架覆盖是否缺少必要的测试用例。每个检查项在执行时都要求Skill输出三样东西检查了什么、发现了什么、依据是什么。这个“依据”就是证据链的核心。比如“依据第42行新增的判空逻辑只覆盖了null未覆盖空字符串而该参数在上游第18行可能被赋值为空字符串”。3.3 风险分级让“必须改”和“可以放行”一眼可辨有了检查项和证据下一步就是风险分级。没有分级评审意见就是一锅粥。我采用的是一个简单的三级模型等级含义处理建议阻断存在明确的逻辑错误、安全漏洞或数据风险必须修改后才能合并警告存在潜在问题或不符合规范但当前场景下不一定触发建议修改需人工确认提示风格、可读性、优化建议可选择性处理分级的关键在于判断标准要写死在Skill里不能靠模型自由发挥。比如“未处理的异常分支”一律归为阻断“命名不符合项目规范”归为警告“可以提取为常量”归为提示。标准固定了不同人跑出来的结果才一致评审才有公信力。3.4 输出格式让人能在30秒内抓住重点评审结果最终是要给人看的。如果输出是一大段文字没人有耐心读完。我的做法是让Skill输出一个结构化的评审报告包含变更摘要、检查项逐条结果、风险分级汇总、以及最关键的——每条结论对应的diff位置。这样你拿到报告后可以快速扫一眼风险汇总知道这次改动有没有阻断项。如果没有再挑几条警告看看依据确认可以接受就可以合并了。整个过程从“逐行读diff猜风险”变成了“看结论验证依据”效率完全不一样。4. 落地一个评审Skill从环境准备到跑通第一次评审4.1 先想清楚Skill的边界别一上来就贪大我见过不少人做评审Skill一上来就想让它什么都能查性能、安全、架构、文档、测试覆盖率全包。结果就是每个维度都做得浅输出一堆泛泛而谈的意见反而没人用。我的建议是第一版只做正确性和一致性两类检查。这两类是最刚需的也是最容易做出证据链的。正确性检查依赖diff和函数上下文一致性检查依赖项目里的既有模式这两块的数据都比较好拿。等你把这两类跑顺了评审报告有人看了再逐步加影响面、安全性这些维度。边界清晰的另一个好处是你可以明确告诉使用者“这个Skill不负责性能评审别拿它当性能工具用。”预期管理做好了信任感反而更强。4.2 准备评审所需的上下文数据Skill要跑起来需要几样输入。第一是git diff这个直接通过命令拿就行。第二是项目结构信息至少要知道哪些目录是源码、哪些是测试、配置文件放在哪。第三是项目的规范约定比如命名风格、错误处理模式这些可以整理成一个简短的规范文档喂给Skill。这里有个实操细节diff的获取方式会影响后续解析。我建议用git diff --unified5把上下文行数调大一点。默认的3行上下文有时候不够判断5行能覆盖大多数场景又不至于让diff太长。如果改动特别大可以按文件拆分逐个评审避免一次性输入过多导致模型注意力分散。# 获取带上下文的diff输出到文件供Skill读取 git diff --unified5 HEAD~1 HEAD /tmp/review_diff.txt # 如果只想评审暂存区的改动 git diff --unified5 --cached /tmp/review_diff.txt4.3 把检查逻辑写成明确的指令而不是模糊的期望Skill的核心是一组指令。写指令的时候最容易犯的错是写得太模糊比如“请仔细检查代码质量”。模型看到这种指令只能自由发挥。正确的写法是把每个检查项写成可执行的判断步骤。举个例子正确性检查里关于边界条件的指令我会这么写对于diff中每个新增的条件判断检查其覆盖的分支是否完整。具体步骤1识别条件表达式的所有可能取值2检查每个取值是否有对应的处理分支3如果存在未处理的分支输出该分支的触发条件并标记为阻断或警告。这样写模型就知道该干什么、按什么顺序干、输出什么。证据链也就自然形成了因为每一步都有明确的检查对象和判断结果。4.4 第一次跑通用一个真实的小改动验证环境准备好之后别拿一个大重构来试。找一个最近的真实小改动比如修了一个bug、加了一个参数校验用这个来跑第一次评审。跑完之后重点看两件事。第一它有没有漏掉你已知的问题。如果你明明知道这个改动有个边界没处理但Skill没报出来说明检查项或者上下文有问题。第二它报出来的问题依据是否成立。如果它说某行有问题你去看那行发现它理解错了说明指令需要调整。我第一次跑的时候Skill把一个正常的空值检查误报成了“冗余判断”依据是“该参数在上游已保证非空”。我去查了上游代码发现上游确实有判空但那个判空在某个分支下会被跳过。这就是一个典型的“证据链不完整导致的误报”。后来我在指令里加了一条判断参数是否可能为空时必须追溯所有上游赋值路径不能只看最近的一处。误报就消失了。5. 让评审结论真正可信的几个关键设计5.1 每条结论都必须能定位到具体的diff行这是证据链最基础的要求。评审报告里的每一条意见都要带上它对应的文件、行号、以及diff里的那段代码。没有定位的意见一律不输出。实现上可以在Skill的指令里强制要求输出格式包含file、line、snippet三个字段。模型在生成意见时必须先从diff里找到对应的位置再生成结论。这个约束看起来简单但效果非常明显它逼着模型“先看代码再说话”而不是“先说话再找代码”。5.2 区分“事实”和“推断”别把猜测当结论AI评审最容易让人不信任的地方就是它把推断说得像事实一样。比如它说“这里会导致内存泄漏”但其实只是“在某些极端情况下可能”。这种表述会让人要么过度紧张要么发现一次不准之后就再也不信了。我的做法是在Skill里明确要求区分事实陈述和推断陈述。事实是“第30行打开的文件句柄在第45行的异常分支中没有关闭”推断是“如果该异常分支被触发可能导致句柄泄漏”。事实用肯定语气推断用条件语气并且标注推断所依赖的假设。这样读者能清楚地知道哪些是确定的、哪些是需要自己判断的。5.3 用项目自身的模式作为一致性判断的基准一致性检查最怕的是拿一个通用的“最佳实践”去套所有项目。每个项目都有自己的风格A项目用早返回B项目用嵌套if没有绝对的对错。所以一致性检查的基准应该是项目自身已有的模式而不是外部标准。具体做法是在评审前先让Skill扫描项目里同类代码的写法提取出模式然后用这个模式去对照新改动。比如项目里所有的数据库操作都用try-with-resources那新代码如果用了手动close就报一致性警告。这个基准是从项目里来的所以结论天然有说服力。5.4 把“无法判断”也作为一种合法输出这一点特别重要但很多人会忽略。有些改动光看diff和有限上下文确实判断不了有没有问题。比如它调用了一个外部服务的接口但接口的行为没有文档。这时候强行给一个结论反而是有害的。我在Skill里加了一条规则当证据不足以支撑任何结论时输出“需要人工确认”并说明缺少什么信息。比如“该改动调用了X接口但项目中未找到该接口的契约定义无法判断参数类型是否匹配建议人工确认”。这种诚实的输出比一个瞎猜的结论有价值得多也更能建立长期信任。6. 实操中踩过的坑和对应的解法6.1 diff太大导致评审质量断崖式下降我遇到的最大的坑就是一次性评审一个几百行的diff。模型在处理长输入时注意力会分散前面的检查项和后面的代码对不上证据链直接断裂。表现就是它给出的行号错位或者把A文件的问题安到B文件上。解法很简单按文件拆分评审。一个文件一个文件地跑每个文件的diff单独作为输入。如果单个文件的改动超过200行再按函数或代码块进一步拆分。拆分之后每个评审单元小、上下文清晰证据链的准确性大幅提升。代价是评审次数变多但这个可以用脚本自动化不影响使用体验。6.2 模型倾向于“报喜不报忧”或者“过度报警”这是两个相反的极端但根源是一样的指令里对风险等级的判断标准不够明确。当标准模糊时模型要么倾向于说“没问题”来显得友好要么倾向于报一堆警告来显得严谨。我的解法是把风险等级的判断写成决策树。比如如果改动导致某个已有测试用例失败 → 阻断如果改动引入了一个新的外部依赖且未在依赖文件中声明 → 阻断如果改动修改了公共接口签名但未更新所有调用点 → 阻断如果改动中的命名与项目同类代码不一致 → 警告如果改动可以简化但当前写法没有错误 → 提示决策树写清楚之后模型的输出就稳定了。同一个改动跑十次风险等级基本一致。6.3 评审意见和实际代码对不上号这个坑的典型表现是Skill说“第50行的变量未初始化”你去看第50行发现是一个完全无关的语句。原因是模型在生成意见时没有严格地从diff里取行号而是凭记忆或推测填了一个。解法是在指令里强制要求每条意见生成前必须先引用diff中的原始代码片段再基于该片段生成结论。也就是说输出顺序是“代码片段 → 分析 → 结论”而不是“结论 → 找代码”。这个顺序的调整让行号错位的问题基本消失了。6.4 团队里不同人跑出来的结果不一致如果Skill的指令里有模糊地带不同人使用时可能会加自己的理解导致结果不一致。比如有人把“潜在问题”理解成警告有人理解成提示。解法是把Skill的指令版本化像代码一样管理。每次修改指令都记录改了什么、为什么改。团队统一使用同一个版本的Skill评审标准就一致了。另外在指令里尽量避免“可能”“也许”“视情况而定”这类词能用确定规则的地方就用确定规则。7. 把评审Skill接进日常工作流的几种方式7.1 本地预评审合并前的第一道过滤最轻量的接入方式是在本地提交前跑一次评审。你可以写一个简单的脚本把当前分支和主分支的diff拿出来喂给Skill输出评审报告。开发者自己先看一遍把阻断项处理掉再提PR。这种方式的好处是反馈快不用等CI。而且开发者自己跑评审时心态是“我想知道有没有问题”而不是“别人要挑我毛病”接受度更高。我自己的习惯是每次git commit之前跑一次花不了一分钟但能挡掉大部分低级问题。7.2 CI环节的自动评审作为合并门禁的一部分如果团队有CI流程可以把评审Skill接进去作为PR的一个检查项。CI跑完之后评审报告作为评论贴到PR上。如果存在阻断项就阻止合并。这里要注意的是CI里的评审应该是增量的只评审这次PR引入的改动而不是全量扫描。另外阻断项的判定要保守一点只拦那些确定性的问题避免因为误报导致开发者频繁绕过门禁。一旦门禁被频繁绕过它的权威性就没了。7.3 和Agent结合让评审成为自动化流程的一环再往前走一步可以把评审Skill封装成一个Agent能力。比如在一个自动化流程里Agent负责拉取diff、调用评审Skill、根据评审结果决定是自动修复还是通知人工。关键词里的Agent、agent skill、agent开发说的就是这个方向。不过我的建议是自动修复要非常谨慎。评审Skill可以给出修复建议但自动改代码这件事风险比评审本身大得多。至少在现阶段让Agent做“评审建议”人来决定“改不改、怎么改”是更稳妥的分工。8. 关于评审Skill我自己的几条使用心得用到现在我最大的体会是评审Skill的价值不在于它找出了多少问题而在于它让每一次合并决策都有了可追溯的依据。以前合并代码靠的是“我觉得没问题”现在合并代码靠的是“评审报告显示没有阻断项两条警告我已确认可接受”。这个转变才是“敢不敢合并”这个问题的真正答案。另外几条零散的经验。第一别指望一次把Skill调到位。我前后改了十几版指令才让误报率降到可接受的范围。每次遇到误报或漏报就回去改指令慢慢迭代。第二评审报告要存档。每次合并前的评审报告保留下来后面如果线上出了问题可以回溯当时评审时看到了什么、判断是什么这对复盘非常有价值。第三人工评审不能完全取消。Skill负责的是可结构化的检查项那些需要业务理解、架构判断、跨团队协调的部分还是得人来。Skill是放大器不是替代品。最后分享一个我最近在用的技巧把评审报告里的“提示”级别意见攒起来每周集中看一次。这些意见单独看都不紧急但攒在一起往往能发现一些系统性的改进点比如某个命名习惯反复出现不一致那就值得在团队里统一一下。这种用法让评审Skill从“合并前的检查工具”变成了“持续改进的输入源”价值又多了一层。