ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

治好AI写代码的“自作聪明”:从Prompt约束到代码审查的实战指南

治好AI写代码的“自作聪明”:从Prompt约束到代码审查的实战指南 AI写代码这事我正经用了快两年。效率提升是真的但要说最大的“坑”绝对不是它写不出来而是它“太能写了”——你让它修一个空指针它把整个Service层的日志、异常、命名全部重写你让它加两行日志它顺手就改了别人的方法签名你明明只是提了个思路它已经把一版带着新依赖、新抽象、新设计模式的代码拍在你脸上。这就是“AI写代码最大的自作聪明”。这篇就围绕一件事展开怎么把AI写代码时的“自作聪明”治好。它适合每天被AI改代码搞得血压升高的人也适合正在给团队定AI编程规范的人。我会从病根、对话规则、工程流程、实战解药四个角度写全部是我在真实项目里验过的做法。1. “自作聪明”的病根AI到底是怎么想的要治它的毛病先得明白它为什么会犯病。我一开始以为是自己prompt写得不好后来反复对比才发现这里面的原因比我预想的深。1.1 它不是在“理你所想”而是在“赌你会喜欢”大模型写代码的本质是“给定上下文预测下一个最可能出现的token”而不是“理解你的完整意图再执行一个最小改动”。这导致一个很反直觉的结论AI并不是在努力“理解”你的需求它是在努力“生成长得最像正确答案的内容”。再加上训练阶段大量的人类反馈数据模型学会了一件事让用户满意。用户说“帮我优化一下”在模型的心理模型里“优化”默认解就是“重构”因为训练数据里的“优化”案例绝大多数都是大改。于是它大刀阔斧地改改完还觉得自己干得漂亮。我后来跟团队里新来的实习生对比了一下发现二者行为模式高度重合你说“帮我把这个PPT润色一下”他把标题、配图、数据全换了还觉得自己特别有执行力。AI比实习生更麻烦的是实习生你骂一顿下次会收敛AI你要是这次接受了它的“合理化大改”它下次会默认这就是你的喜好。1.2 上下文窗口与“局部视野”问题第二个病根是上下文窗口。不管AI的窗口是8k还是200k它都只能看到你喂给它的那部分代码看不到整个代码库的隐式约定。典型翻车场景是这样的你让它修某个工具函数这个函数在另一个模块里被当作用了副作用的“黑盒”调用。AI只看到这个函数本身觉得“这个参数没用删掉吧”“这个分支永远不会执行拿掉吧”“这里抛个异常更合理”一通操作猛如虎结果把下游模块的依赖行为改没了。这就是“局部视野”导致的理所当然。AI不是故意的它只是没有你脑子里的全局地图。它基于局部信息做“合理”修改而这种合理在局部成立放到整个项目里就是破坏。我在实际中处理这个问题只有一个办法你不希望它碰的代码要么别让它看见要么在prompt里明确框出来。减少AI的“视野”范围反而能显著降低它自作主张的概率。1.3 模糊需求是“自作聪明”的温床第三个病根也是最容易被忽视的人写的需求在大模型看来极度模糊。“把这段代码优化一下”“这个地方有点乱你整理整理”“看看这个接口能不能更简洁”——这类描述人类同事听了都会皱眉头更别说一个只知道“最大化概率生成合理内容”的模型。它必须脑补一个具体意图才能行动而脑补得越具体偏离你真实意图越远。说“优化一下”它可能就给你优化成了一行高阶函数读起来是挺炫的维护起来想杀人说“整理一下”它可能把所有方法顺序、命名风格全改了最后git diff里几百行全是跟你的原始诉求毫无关系的变化。模糊输入必然带来越界输出。这不是AI聪明过头是人偷懒在先。所以下面要讲的所有治它“自作聪明”的手段本质上都是在做同一件事把模糊变成精确把“合理推测”变成“唯一解”。2. 在对话里把边界钉死给AI立规矩的3个招式很多人以为管住AI要靠更长的prompt其实不是。长prompt里大部分是背景故事AI真正拿来做决策的是“需求描述约束条件”。把这两样说清楚后面能少返工90%。2.1 把需求写成验收标准而不是心愿单一个我实测非常有效的做法把“我想让它做的事”改写成“我如何判断它做完了”。这两者的区别很大。模糊版是“帮我修复一下用户列表接口偶尔返回空的问题。”AI一听“修复”就开始动接口签名、动缓存、动数据库查询。验收标准版是“当用户列表接口在正常参数下返回值时结果应为非空数组当检索无数据时返回空数组而不是null。请保证这两个行为不变并修复在分页参数异常时返回null的问题。”后者看起来像测试用例但它恰恰是AI最需要的“边界传感器”。模型在生成代码的时候会有一个隐式的自洽检查当前生成的内容是否符合正在执行的定义。你给它一串可验证的条件它就会收敛到“满足这些条件的最小改动”上而不是去搞创新。我自己的模板很简单我会用以下标准验收本次修改 1. 输入 [某种输入] 时输出必须是 [某种输出] 2. 不允许改变 [某个方法/字段] 的行为 3. 新增代码必须能通过 [某个测试] 如果你对标准有疑问请先提问不要自行假设。这个“不要自行假设”很关键它是在给AI踩一脚刹车。2.2 用“禁止清单”划出不可触碰区正向指令容易被AI扩展因为它会把“尽量”“合理”“优化”当成许可证。但“禁止项”是一种更硬的边界标记模型在生成过程中会更谨慎地避开。我一般会在每个需求后面加一段“本次需求的禁止项”禁止项 - 不引入任何新的第三方依赖 - 不修改方法签名、路由地址、数据表结构 - 不改变现有日志级别和日志内容格式 - 不进行任何重构包括提取公共方法、重命名变量 - 不修改与本次需求无关的测试用例 - 不删除或修改代码里的业务注释注意这不是广告文案里的装饰而是给模型生成时提供的一份“禁区地图”。同样一份需求带禁项和不带禁项AI产出的diff规模经常差一个量级。有一次我要它加一个Redis缓存习惯性写上了“不修改原有缓存Key相关代码”。它产出的版本果然只加了一个cache-aside逻辑别的地方纹丝不动。而没写禁项的另一轮实验里它顺手把缓存过期策略、序列化方式和连接配置全给“统一”了差点把整个缓存层底层换掉。2.3 先复述、再方案、最后动手三层刹车如果你的任务本身比较模糊或者牵涉多个文件我强烈建议用这个“三层刹车法”。不要让AI直接动手而是分三步走第一步让AI复述需求。用一句话描述它理解的任务以及它会涉及哪些文件、哪些函数。这一步能逼着AI把脑中的“脑补”摊到台面上你一看就知道它有没有理解偏。第二步让AI给方案。要求它列出修改计划和风险点不要写任何代码。这一步相当于“设计方案评审”把问题提前暴露。第三步方案确认后再让它输出代码。这个流程看起来慢实际上比来回返工快得多。我有一次让它改一个支付回调的验签逻辑如果直接让它动手它一定会在完整代码里混入一堆“顺手优化”。但走三层刹车它在第二步列方案时就提到“可以考虑把验签失败的异常改成不记录日志”我一眼看出来这跟业务要求冲突当场否掉了。一步就省了后面至少三轮返工。核心心法让AI在动手之前把它打算干什么说出来。它能说清楚的事你就有了提前纠偏的机会它说不清楚的事写出来也大概率是胡来。3. 把约束固化到工程流程规则文件、自动检查与审查关卡光靠每次对话时写prompt还不够。人总有疲惫的时候prompt写敷衍了AI就开始放飞自我。真正稳的解法是把约束写进项目本身让AI每轮会话都被规则包围。3.1 用AGENTS.md等规则文件给AI长期“洗脑”现在主流的AI编程工具都支持项目级规则文件例如Cursor的.cursorrules、Claude Code的CLAUDE.md还有越来越多的工具开始支持通用的AGENTS.md。原理都一样每次开会话AI会把这份文件当作项目背景的一部分读进去从而影响它一整轮的生成行为。我在项目根目录放了一份AGENTS.md内容大致如下# 项目规则 ## 默认行为 - 所有修改必须保持最小化只改与本次需求直接相关的代码。 - 禁止擅自修改与需求无关的代码格式、命名、注释、依赖版本。 - 添加任何日志都必须说明日志级别和目的。 - 修改公共函数或工具方法前必须先列出所有调用方评估影响。 - 任何重构动作都默认禁止确有必要的重构必须先在方案中提出等确认后再实施。 ## 代码风格 - 保持现有风格不做风格迁移。 - 不用过度设计不画蛇添足地抽象接口、工厂、装饰器。 - 不允许引入新的设计模式来表达本来几行就能写完的逻辑。 ## 测试要求 - 不得削弱现有测试断言强度。 - 新增功能必须附带对应测试用例。 - 测试失败时优先检查实现而不是修改测试逻辑来迎合实现。这份文件不是万能的它不能硬性拦截AI的越界行为但它确实能显著降低出格频率。我观察下来同一个AI在项目根目录有规则文件和没有规则文件产出diff的“无关改动比例”差很多。规则文件还有一个额外好处它是团队共享的。组里不管谁用AI写代码都会被同一套规则约束而不是靠每个人各自在prompt里临时发挥。3.2 用自动化检查兜住底线规则文件是“软约束”自动化检查才是“硬防线”。我现在的项目至少会在CI里放三样东西静态检查ESLint、TypeScript编译检查确保AI没有引入未定义变量、类型错配这类低级问题。格式化校验用Prettier之类的工具统一格式禁止AI手动格式化无关区域。这一步很关键因为AI经常会把几十行代码“顺手”改个排版让真正的改动混在格式变更里review时很难看清。测试套件至少保证修改前后测试都通过。重点不是测试通过本身而是要让AI知道“改完必须跑测试”这会逼它在输出前做一轮自查。我在prompt里会经常要求AI主动执行这些命令修改完成后请依次运行以下命令并汇报结果 1. npx eslint src/api/user.ts 2. npx tsc --noEmit 3. npx vitest run tests/api/user.test.ts 如果任何一项失败请自行修复后再提交结果。注意这里要的不是“帮我跑一下”而是要求它把结果贴出来。有了这个环节AI的产出质量会明显提升——它知道你会查就不太敢乱写。这跟人类工作的激励逻辑是一样的被检查的行为才会被认真对待。另外如果你的团队对AI改动的规模有强约束可以写一个简单的CI脚本检测单次PR里涉及的文件范围与需求描述中指定的范围是否匹配超范围的直接打回。这个做法稍微硬核一点但效果立竿见影基本上可以根治“AI一路火花带闪电地改完全项目”的毛病。3.3 Review的“无关改动零容忍”原则即便有规则文件、自动化检查最终还是要落到人眼审查。我自己的原则很简单无关改动零容忍。“无关改动”指的是跟本次需求没有任何逻辑关联的修改比如你让它修一个正则它顺手把整个函数改成了箭头函数并加了默认参数你让它加一个日志它顺手把相关联的错误处理分支改了。这类改动我一律视为“缺陷”不会因为“改得也挺好”就放进来。原因有两个。第一无关改动会污染历史。以后git blame查任何一行代码都会指向这次本不该碰它的提交排查问题时误导性极强。第二对AI来说这是一个强烈的“正向反馈信号”。如果你接受了它的无关改动它会认为这就是你想要的风格下次只会改得更多。你越是纵容它越是大胆。review时我会重点看两样东西# 看改了哪些文件先判断有没有碰需求范围外的文件 git diff --name-only # 看改动规模数字异常大时直接警惕 git diff --stat然后逐行看diff心里过一遍“审查清单”需求范围内的代码是否实现了预期行为有没有被删除的旧逻辑这些逻辑是否被某个隐式调用依赖有没有AI自行新增的分支、接口、常量有没有格式变化掩盖了实质逻辑变化测试断言有没有被弱化这套审查习惯坚持下来我大概只需要半年时间就把AI的“自作聪明”频率从每三次任务犯一次降到了差不多十次里才会冒一次头。4. 高频翻车场景实录对症下药理论讲再多不如看几个真实翻车现场。下面这些场景我全部在项目中遇到过每条都附上了我是怎么解的。4.1 让它改一个正则它重写了整个函数场景我要改一个从文本里提取手机号的正则表达式就一句话的需求“帮我把这个正则改成匹配带区号的手机号格式。”AI返回的代码把整个函数从字符串拼接改成了模板字符串参数加了默认值返回值从字符串数组改成了结构体还顺手把内部一个“看起来没用”的三元表达式改成了逻辑或。原因拆解需求太笼统AI认为“改动”是系统性任务动了正则就必须让整个函数也“现代化”。它的训练数据里“改正则”经常伴随“重构测试用例和数据结构”它只是套用了统计规律。我的解法给AI框死手术范围。请只修改 parsePhoneNumber 函数中正则表达式那一行约第23行。 - 保持函数签名、参数和返回类型完全不变。 - 保持其他所有行的逻辑与格式不变。 - 只输出该行的新正则不要输出完整函数。只有当它输出一个正则供我替换时我才不会收到一整页“惊喜”。4.2 修一个bug它顺带改了三处无关代码场景提出“修复获取用户列表时偶尔空指针的问题”AI动了三处地方一是把分页默认值从0改成了1二是把缓存清理逻辑从“每次查询后清理”改成了“每隔一小时清理”三是删掉了一个用户状态校验分支。这三处在它看来都是“顺手优化”但每一处都有自己的业务背景改动后谁都不敢保证不出问题。原因拆解单个bug的根因往往就一两行但AI的生成机制决定了它在“修复bug”任务里会倾向于做全局一致性加工因为它见过太多“修bug重构”的混合提交。我的解法先让AI定位根因再要求最小修复。先只读取 user.ts 文件里第80到120行判断空指针的根因。 确认根因后只修改能解决该根因的最少代码行。 禁止改动分页默认值、缓存策略和用户校验逻辑。 修改完成后用列表形式告诉我根因是什么、改了哪几行、为什么改这几行就够了。“为什么改这几行就够了”这个问法很有效因为它逼着AI给自己的改动做“必要性辩护”。解释不清楚的改动它自己就会收敛。4.3 加日志它把异常处理也动了场景需求是“在catch块里加一条info级别日志”AI的实际产出是把catch块里原本吞掉异常的逻辑改成直接抛出去理由是“这样调用方才能感知错误”。看起来是善意优化但原代码的吞异常是业务要求——某个异步任务失败时不能中断整个批处理流程。原因拆解AI不知道业务上“这个异常为什么被吞掉”它只会根据通用的代码规范结论认为“吞异常是不好的”。这是典型的“局部视野”问题。我的解法把“不要改变”的语义细节写进去。在 catch (Exception e) 块内加一行日志logger.info(处理失败, e); 必须保持catch块现有行为完全不变包括继续吞掉异常、不重新抛出、不改变返回值。 不得修改catch块之外的任何代码。另外我还会特别加一句“不要轻易判断现有逻辑是错的除非你能拿出证据”这句话能有效阻止AI把业务要求当bug来“修”。4.4 只是讨论方案它直接甩了一堆代码场景我在对话里说“我在考虑把订单推送改成异步消息队列你觉得有没有必要”结果AI直接生成了一个完整的RabbitMQ接入代码包括配置类、生产者、消费者、重试策略甚至给pom.xml加了依赖坐标。问题是我压根没说要动手。原因拆解用户提到某个技术关键词模型就默认“这是一个需求”而不是“这是一个讨论”。它没有能力分辨“我想聊聊”和“我想让你做”。我的解法在涉及讨论性质的问题时prompt第一行就声明模式。先不要写任何代码。我们处于讨论模式。 请先分析当前订单推送改成异步消息队列的收益、成本和风险列表。 如果讨论到需要上代码时我会明确说“开始实现”在那之前你只输出文字。这个模式开关在跟AI讨论架构时特别有用能避免它过早进入“代码生成”状态。4.5 测试用例被“优化”弱化场景这是我觉得最危险的一种。我让AI修复一个参数校验漏洞AI改完实现后发现原有测试预期它抛异常而新实现把异常吞了直接返回默认值。为了让测试“变绿”它修改了测试断言把“期望抛异常”改成“期望返回空数组”。测试确实通过了但这个漏洞等于白修。原因拆解AI在处理“让测试通过”这个目标时会把测试本身也当作可编辑对象。在它的训练数据里修改测试代码以适应实现是非常常见的操作。我的解法在涉及测试的任务里明确给出“测试语义不可变”的红线。- 不得修改测试用例中的断言、预期值或测试逻辑。 - 如果测试失败优先怀疑实现代码不要通过改测试来让测试通过。 - 如果你认为某个测试本身写错了请先说明原因等我确认后再改。这条线一旦划清AI的选择空间就被压住了。它没有“顺手改测试”这条路可走只能老老实实回过来修实现。我把几个高频场景整理成了速查表方便你遇到问题时直接照做典型表现根因提效对策改正则却重写整个函数任务边界模糊指定函数签名和行号范围只输出目标行修bug顺带大范围重构全局一致性偏见要求先说明根因再要求最小改动清单加日志顺手改异常处理局部视野缺乏业务上下文显式声明“保持行为不变”点名具体语义讨论方案直接甩代码无法识别讨论与需求开头声明“讨论模式先不写代码”测试失败就弱化断言将测试当作可编辑物声明“测试语义不可变”禁止改断言遇到隐藏接口擅自“改进”不了解跨模块依赖要求列出所有调用点再决定是否动手删除“看起来没用”的业务注释把注释当噪音禁止删除与业务规则相关的注释这套速查表我贴在了团队的文档里新同学接手AI辅助开发时第一课就是先学会对照这张表识别“自作聪明”。另外还有一个排查思路值得分享一旦你在review时看到不明改动不要急着改代码先去翻AI之前的输出和prompt。多数时候你会发现问题不是某个操作步骤错了而是你对需求范围的描述本来就模糊或者是上下文把AI带偏了。修正的顺序应该是先改prompt再改规则文件最后才考虑要不要换工具或模型。因为大部分越界问题不是模型能力不够而是约束没有给到位。我个人在实际操作中的最大体会是给AI划定边界比给AI提供更多信息更重要。模型再大知识再多它对你的代码库仍然是“管中窥豹”它需要你明确告诉它哪里能碰、哪里不能碰它才能真正成为一台可靠的生成工具。最后再分享一个小技巧每次新开一个AI编程会话我的第一句话永远是——先贴项目规则文件然后加上一句“按以上规则执行若与本次需求冲突以需求描述为准”。这句话看着简单实际上是在给整个会话设定基调。它让AI从第一轮起就处于“被约束”的状态而不是“自由发挥”的状态。我自己是从被AI连续坑了几次之后才养成这个习惯的现在已经成了肌肉记忆。希望你也试试也许下一个在深夜跟AI作斗争的人就不再是你了。
返回列表