1. 为什么我要把代码审查和微重构交给Prompt
1.1 代码审查的痛点和微重构的真正难点
先说个背景。我所在的团队每个迭代都有大量 PR 要过,reviewer 时间被切得很碎,大家水平也参差不齐。代码审查看起来是“找茬”,其实非常消耗脑力:既要盯业务逻辑对不对,又要关注命名是否清晰、边界条件有没有覆盖、异常处理是否到位、有没有隐藏的性能问题,还得顺便判断哪些地方可以小改一下,让后面维护的人少踩坑。人的注意力是有限的,连续看几十个 diff 之后,很容易进入“视觉疲劳”状态,这时候漏掉一个 P0 级别的逻辑漏洞,一点都不奇怪。
更难的是“微重构”这一步。不是说团队不知道哪里该改,而是“不敢动”。代码在线上跑得好好的,你让我为了“看起来更清爽”去改它?万一改到一个隐蔽依赖怎么办?所以在大多数团队里,重构只发生在“不得不动”的时候:加新需求、修线上 bug、或者被技术债逼得没办法。我也一样,长期带着“能不动就不动”的心态写代码,直到我把这套代码审查微重构 prompt 跑起来,心态才慢慢转变。
大模型在这里特别适合当“低成本、高耐心的第二双眼睛”。它不会因为连续看十段代码就烦躁,也不会因为“这段代码是老同事写的”就不好意思提意见。更重要的是,只要你把审查维度和重构纪律写清楚,它每次给出的输出质量是稳定的。这恰好是提示词工程(prompt engineering)真正有价值的地方:不是把问题丢给 AI 等答案,而是把专家的审查经验、团队规范沉淀成一段可复用的指令,让模型按你的标准工作。
1.2 提示词工程在代码场景里的设计思路
prompt 工程的核心其实就四件事:角色、任务、输出格式、约束边界。
角色决定模型用什么视角看问题。你让它当“刚入职的实习生”和“带了十年团队的技术负责人”,它挑出来的毛病完全不一样。任务决定检查广度,是只看逻辑问题,还是连命名规范、重复代码、性能隐患一起查。输出格式决定结果能不能直接落地,如果 AI 给你一堆长段落,你还得自己重新整理一遍,那不如不用。约束边界是防止它自由发挥,比如“不许改接口”“不许新增依赖”“不要重写整个文件”。
这套框架放在任何领域都通用,但用在代码审查场景里格外合适,因为代码审查天然有明确的标准和边界。你要是没加约束,很容易出现一种情况:AI 审查完以后,顺手把你整个函数重写一遍,然后补一句“这样更优雅”。我下运行时真的遇到过,后面会细讲。
2. 一套能直接抄的“代码审查微重构”Prompt模板
2.1 模板全文
这里直接给出我目前在使用的一套模板。它适合大多数中大型函数、类或单文件模块的审查,也可以按语言和场景做小调整。复制下来以后,把花括号里的内容替换成你的真实信息就行。
# Role 你是一名拥有十年以上一线开发经验的资深软件工程师,精通代码走查与微重构实践。 你擅长在不改变代码外部行为的前提下,发现代码中的可读性、可维护性、健壮性问题, 并提出小步、安全、可执行的优化方案。 # Context 我正在开发的项目是:{一句话介绍项目} 代码所在的文件和模块是:{文件路径/模块名} 代码用途:{这段代码做了什么} 这段代码会被以下位置调用:{整理已知的调用方,没有就写“未知”} # Task 请你对下面给出的代码执行“代码审查 + 微重构建议”两个步骤。 第一步:代码审查 从以下维度逐项检查,不要遗漏: 1. 可读性:变量/函数命名是否清晰;函数是否过长、嵌套是否过深;注释是否必要且准确。 2. 职责单一:函数是否做了不止一件事;模块边界是否被破坏。 3. 重复代码:是否存在可以安全抽取的重复片段。 4. 边界条件:空值、空集合、极端输入、并发场景是否被正确处理。 5. 错误处理:异常是否被吞掉;错误信息是否有意义;资源是否被正确释放。 6. 性能隐患:是否有循环内重复计算、不必要的数据拷贝、明显的高复杂度操作。 7. 潜在Bug:是否存在逻辑漏洞、竞态条件、状态污染等问题。 第二步:微重构建议 针对你发现的问题,按以下纪律给出建议: - 只做“微重构”:单次改动的行数尽量控制在30行以内,不改变任何外部行为。 - 不允许修改:类名、方法签名、接口协议、依赖关系、业务规则。 - 不允许新增第三方依赖。 - 优先给出“低代码风险、高代码收益”的改动。 - 如果一个建议会触及多个调用方,不要给,换成更局部的小改动。 # Output Format 先输出总体评价,不超过3点,每点不超过20字。 然后输出一个 Markdown 表格,列名如下: | 严重级别 | 代码位置(行号) | 问题描述 | 影响 | 微重构建议 | 严重级别使用:P0(必须修,可能引发bug)/ P1(建议修,影响维护)/ P2(可优化,锦上添花)。 表格每一行的“微重构建议”必须给出具体可执行的改法。对于其中最重要的2个问题, 在表格后面补充“改动前 / 改动后”的代码块示意,不要超过15行。 最后输出两列行动清单:“改动项”和“预计影响范围”,明确标识每个建议是否可能影响其他调用点。 # Constraints - 不要重写整个代码块,只审查我贴出来的部分。 - 不要给与代码无关的大道理。 - 如果信息不足,直接说明缺少哪些上下文,不要编造。2.2 模板拆解:每个模块为什么这样设计
角色设定部分,我特意强调“资深工程师”和“微重构实践”,是为了让模型默认采用“稳妥优先”的视角。如果你让它当“刚入职的初级程序员”,它很容易给你一堆正确但没用的建议,比如“建议加注释”。如果你让它当“架构师”,它可能张口就是“建议引入某某设计模式”,完全脱离小步重构的克制感。
审查维度那七项,基本覆盖了日常代码审查最常犯的错误。很多人写 prompt 只写一句“帮我 review 这段代码”,模型就只会泛泛而谈,有时候连低级的参数空指针问题都发现不了。把维度一条一条列出来,相当于给模型一张检查清单,让它一项一项过,覆盖面会明显提升。
微重构纪律是最关键的部分。你注意看“如果一个建议会触及多个调用方,不要给”这句,这是我和 AI 反复对抗以后总结出来的。模型特别喜欢“抽一个公共函数然后让所有地方都调用”这种理想方案,但它根本不知道这个项目里函数被谁调用,贸然把建议写成“抽取公共函数”,很可能把一个低风险微重构变成了全链路高风险改动。加了这条约束以后,输出明显“怂”了很多,也安全了很多。
输出格式上,P0/P1/P2 的分级标准是借鉴了故障定级思维。P0 表示不改会出问题,P1 是影响长期维护,P2 是可做可不做。有了分级,你拿到结果以后不需要重新判断优先级,直接按级别排计划即可。表格里的“代码位置(行号)”看起来很简单,实际很重要,它逼着模型给出精确位置,而不是说一句“有一段代码”。
3. 实操全过程:从翻车到稳定的三版迭代
3.1 拿一段真实代码测试
光讲模板没意思,我来演示一遍完整过程。假设我正在处理一个用户注册保存的接口服务,代码长这样(刻意保留了常见问题):
# 原始示例:save_user 函数 import requests def save_user(data, notify=True): if data is not None: if 'name' in data and data['name'] != '': user_id = data['id'] if 'id' in data else 0 cleaned = { 'name': data['name'].strip(), 'email': data.get('email', '').strip().lower(), 'age': data.get('age', 0), 'phone': data.get('phone', '').strip() } if cleaned['email']: if '@' in cleaned['email']: if cleaned['age'] >= 18: result = requests.post('http://internal-api/user/save', json=cleaned) resp = result.json() if resp.get('ok'): if notify: send_notify(user_id, cleaned['email']) return {'success': True, 'id': resp.get('id')} else: return {'error': 'save failed'} else: return {'error': 'age must be >= 18'} else: return {'error': 'email format invalid'} else: return {'error': 'email required'} else: return {'error': 'name is required'} return {'error': 'data required'}一眼看过去至少有这些问题:嵌套太深、职责不单一、email 校验只判断了一个 @ 符号、requests 异常完全没有捕获、result.json() 没有判断响应状态码、notify 参数为 False 时没有导入任何提示却静默跳过。如果是人工审查,可能要反复看到第三遍才会意识到异常处理空缺,AI 则可以在第一次跑的时候把这些全部暴露出来。
我把这段代码贴进上面的 prompt,把 Context 里的项目信息填好,第一次运行的结果让我喜忧参半。
3.2 第一版输出的典型问题
第一版输出确实抓到了很多问题,比如 email 校验太弱、requests 没有异常捕获、嵌套过深,这些都在点子上。但同时也暴露了 prompt 没有约束住的部分:
第一,模型在结尾加了一大段“完整重构版建议”,直接把整个 save_user 函数重写成了 40 行的新版本,这完全违反了“不要重写整个代码块”的精神。虽然没有真正改文件,但它确实提出了“直接把这段替换成”这样带有全量重写意味的建议。
第二,有一条 P1 建议是“建议把校验逻辑抽取到公共函数 validate_and_clean_user_data 中,供后续复用”,听起来很合理,但实际上这个模块只有 save_user 一个调用点,而且跨服务复用的时机完全未知。这种“为了未来而提前抽象”的建议,恰恰是我说的“会触及多个调用方”的高风险改动,应当被约束过滤掉。
第三,有几处行号明显对不上,模型把第 3 行的代码描述成了第 6 行,应该是模型在估算而不是逐行读取。
问题很明显,不是模板没用,而是约束还不够硬。于是我做了两轮调优。
3.3 两轮调优后的变化
第一轮调优只改了约束部分,没有动角色和任务。我在微重构纪律里增加了一条:只能针对我贴出的代码片段内做局部修改,禁止给出跨文件、跨模块的抽象方案;同时在 Constraints 里把“不要重写整个代码块”改成了“禁止输出超过 15 行以上的替换代码”。另外加了一句:如果某条建议需要改动多个调用点,请不要给出,改为建议我手动评估。
第二轮调优我引入了 few-shot 示例。我在 prompt 末尾附了一个很小的例子,展示我期望的“一个问题 + 一条建议”的格式,比如:
示例输入: if len(arr) > 0: return True else: return False 示例期望输出: | 严重级别 | 代码位置 | 问题描述 | 影响 | 微重构建议 | | P2 | 第1-4行 | 可以用布尔表达式替代if-else | 可读性一般 | return len(arr) > 0 |这个 few-shot 非常有用,模型对输出格式的理解立刻稳定了,不会出现“有时给表格、有时给长段落”的漂移。
调优后的输出更聚焦。它照样抓出 email 校验弱的问题,但不再建议“新建一个公共类”,而是说“在当前函数内增加 email 格式判断逻辑,改动范围约 5 行”;对嵌套过深的问题,建议“使用 guard clause 提前返回,并在当前函数内完成,不影响函数签名”。这才是真正可以放进 PR 描述里的微重构建议。
3.4 把建议变成可执行的行动计划
拿到 AI 输出以后,不要直接照着改。我会先做一个“收益 / 风险”排序。原则很简单:
- P0 级问题优先改,尤其是异常捕获和校验漏洞,这类改动通常不影响外部行为,风险低、收益最高。
- P1 级问题挑改动面小的先做,比如“把嵌套改写成 guard clause”,一次只改一个函数。
- P2 级问题统一记到 TODO 列表,攒到下一次相关需求时顺带处理。
比如前面那段 save_user 代码,我的执行顺序是:先给 requests.post 包一层 try / except,再把 email 校验从“判断 @”改成用标准库 email-validator,最后做 guard clause 重构。三步分别提交,每步都有独立的测试覆盖,即使某一步出了问题,回滚也很容易。
4. 常见问题、避坑与多场景扩展
4.1 模型“幻觉”严重,行号对不上怎么办
这是用 AI 做代码审查时最常踩的坑。模型有时候会给出一个看起来很有道理、但代码里根本不存在的问题,最常见的表现是行号估算错误。我第一次跑的时候,发现模型把“缺少异常处理”挂在第 6 行,实际上那段代码在第 12 行。
解决思路有两个。第一,在大模型回答后,不要直接复制行号就提交,要把行号当作线索,而不是事实,改代码前先定位确认。第二,在 prompt 里明确要求“必须引用原始代码片段的至少 5 个连续字符作为位置锚点”,比如“data.get('email', ” 这一段。这样模型必须从原文里找证据,幻觉概率会明显降低。
4.2 模型看不懂项目上下文,给出错误判断
给模型一个完全不认识的业务模块,它经常会从“代码洁癖”的角度提出合并或抽象建议,结果破坏业务语义。比如我见过它建议“把两个长得像的校验函数合并成一个”,实际上这两个函数所在服务的租户隔离逻辑完全不同,合并会导致严重数据越权。
给上下文信息很关键。不需要太长,几句话就行:这个项目是做什么的、这个模块在什么场景下运行、它被谁调用。如果你自己也不清楚调用方,那就直接在 Context 里写“未知”,然后手动在 prompt 里加一句“如果信息不足,不要做跨文件/跨模块的推断”。这两个小动作能挡掉大部分误判。
4.3 建议越改越大,从微重构变成大重构
模型天然有“完美主义”倾向,你让它做微重构,它可能回你一个“这个类应该拆成三个文件”的方案。这不是它不听话,而是“微重构”这个词对模型来说太主观了,它需要更明确的边界。
我在模板里把“单次改动的行数尽量控制在 30 行以内”写成了硬指标,同时规定“如果建议会触及多个调用方,就不要给”。这两条组合起来以后,模型基本不会再给“抽取公共模块供所有服务复用”这类大工程了。如果你还遇到这类输出,可以先检查自己是不是在 Context 里写了太多“这个模块未来可能会被多个团队使用”之类的话,模型会顺着你的话扩展开。
4.4 输出格式漂移和长度失控
有些人用同一个 prompt,第一次输出带 Markdown 表格,第二次输出纯段落,第三次直接开始聊天。这是因为你没有把输出格式钉死。最简单的办法是加一个 few-shot 示例,给它看“输入长什么样、输出就应该长什么样”。其次可以在 Output Format 里加数量限制,比如“P0 不超过 3 条,总条数不超过 8 条”“每条建议不超过 80 字”,防止模型一口气输出 20 条建议把你淹没。
如果一次审查的代码超过 200 行,建议拆成几个小的函数片段分别提交。大模型对超长上下文的注意力分布并不均匀,中间部分很容易被“忽略”。拆成小块以后,每一块都能得到相对完整的审查,我自己实测下来的覆盖率比一起扔进去高不少。
4.5 多语言、多场景下怎么调整审查维度
模板里的七个维度是通用版,遇到不同技术栈需要增删。前端组件的代码审查可以额外加几项:可访问性(是否有 aria 标签)、状态更新是否在 effect 的依赖数组里、是否有明显的重复渲染开销、样式副作用是否可控。SQL 脚本审查可以加:索引是否被隐式类型转换破坏、JOIN 是否比子查询更优、WHERE 条件是否能回表、分页是否稳定。
我现在的做法是把这些维度做成“可插拔”的角色卡。基础模板里放最通用的审查项,针对具体文件在 Task 里追加一段“额外检查项”。这样既保住了通用质量线,又能适配不同项目的特殊要求,比维护十几套完全不同的 prompt 更好管理。
5. 这套Prompt的局限性与我的使用习惯
这套 prompt 用了两三个月,最大的感受是:它适合做第一轮快速筛查,不适合当最终结论。AI 能帮你把大部分常见问题暴露出来,但“业务语义是否被破坏”“架构层面的抽象是否合理”这种判断,仍然需要真正了解项目的人来拍板。我现在的流程是,先把代码丢给 prompt 做一轮初审,拿到问题清单后再人工复核一遍,确认优先级,最后才动手改。
另外一个小习惯是,我会把每次调优后的 prompt 版本存下来,标上日期和改动原因。用 AI 做代码审查这件事,本质上是在不断打磨自己的“审查标准”。你今天觉得合理的约束,跑一个月后发现它挡住了某些场景,那就删掉重调。prompt 工程本来就是一个持续迭代的过程,这不单是写给模型的提示词,也是你团队代码规范的具象化版本。
我自己在实践中最有价值的一步,是给每个审查维度都写了一句“为什么要有这一条”。比如“不允许新增第三方依赖”,不只是因为组件体积,更是因为在很多企业项目里,引入一个新依赖需要走一堆安全审批流程,一个微重构根本不该引入这种成本。当你能把每一条约束背后的因果讲清楚,这个 prompt 才算是真正属于你自己的。