ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI编程防翻车指南:从提示词设计到代码审查的完整实践

AI编程防翻车指南:从提示词设计到代码审查的完整实践

我用AI编程用了大半年,从一开始图新鲜让AI写完整模块,到后来老老实实把任务拆碎了再交给它,中间翻过的车能写满一屏。作为AI应用开发工程师,我现在每天的工作流里至少有六成代码是AI生成的,从接口骨架到单元测试样张都让它参与。但我的Review原则也变了很多:AI生成的代码,默认先怀疑,再信任。这篇文章不整虚的,我想把AI编程过程中最容易踩的坑、最容易被忽略的风险,以及我自己总结出来的防翻车方法,一次性讲清楚。适合正在用或准备大规模用AI编程工具的开发者,特别是AI应用开发方向的工程师参考。

1. AI编程为什么总在关键时刻掉链子

1.1 AI应用开发工程师的真实工作流

AI应用开发工程师和传统后端工程师最大的区别在于,我们每天都在跟大模型交互,把模型能力封装成产品功能。写接口、做Demo、调Prompt、处理幻觉输出、接各家模型API,这些事情里AI编程助手确实能帮上大忙。我自己常用的场景包括:生成FastAPI路由骨架、写SQLAlchemy模型、补单元测试模板、做代码重构、把一段冗长的逻辑压缩成可读性更好的函数。

但AI编程最迷惑人的地方就在这里:它在你熟悉的领域表现惊艳,在你陌生的领域一样自信。一旦你把它当成一个全能协作者,把没有拆解过的完整业务模块直接丢给它,翻车就是大概率事件。我见过不少同事在Demo阶段让AI写整套鉴权系统,跑起来倒是很快,最后安全评审时被查出十几个问题,返工成本比手写还高。

我自己现在把AI编程工具的定位很明确:它是“高配版实习生”,不是“高级架构师”。实习生可以帮你快速搭建、按模板生成、处理重复劳动,但你不能把整个项目的生命线交给一个实习生让他自己拿主意。明确这层关系之后,使用 AI 的方式就会发生本质变化。

1.2 翻车的三大根源:幻觉、知识陈旧与上下文稀释

我觉得很多人抱怨“AI写的代码不能跑”,其实没抓到问题的根子上。AI翻车不是因为工具不行,而是因为大模型的工作原理决定了它会翻车。这里有三件事你需要理解,这也是防翻车指南的理论基础。

第一,幻觉。大模型的本质是“预测下一个token”的语言模型,它不是在运行你的程序,它是在生成一段“看起来合理”的程序。这意味着什么?你问它一个它知识边界外的问题,它大概率不会说“我不会”,而是会根据已有知识编造一个看似严谨的答案。代码层面最常见的幻觉就是:引用一个不存在的函数、编造一个库的接口、生成一段逻辑自洽但根本不工作的代码。

第二,知识陈旧。模型的训练数据是有截止时间的,而技术栈的更新是实时的。AI很容易把老版本SDK的用法写进新项目,甚至生成已经废弃的API。我之前试过一次让AI写JWT相关的工具类,它给我用的依赖版本库接口跟当前主版本完全对不上,一跑就是ImportError,排查了半天才发现是它按旧文档生成的。

第三,上下文稀释。多轮对话超过一定长度后,AI对早期提到的关键约束会逐渐遗忘。比如你最开始说“数据库用PostgreSQL”,聊了几十轮之后它可能在某个文件里默认生成SQLite连接。这种问题很难一眼发现,因为你Review的时候会默认它记得前面说过的内容。

理解这三点,你就会明白:AI生成代码必须被审查、必须被验证、必须在受控范围内使用。跳过这一步,就是在赌运气。

1.3 第一个翻车案例:时区丢了的同步任务

给大家看一个我自己踩过的真实例子。某个跨平台系统需要一个数据同步模块,需求很简单:每隔5分钟从上游接口拉取一次数据,写入本地数据库。这个任务看起来非常适合交给AI,模板化、逻辑单一、没有复杂状态。我让AI生成了一个完整的数据同步脚本,测试环境跑通了,数据也能正常写入。

上线第一天凌晨,数据对不上了。排查了几轮才发现问题:上游接口返回的时间字段是UTC,而数据库里其他模块统一存的是本地时间。AI生成的同步脚本直接把这个字段透传存了进去,导致每天午夜之后,当天数据的统计结果全部错位。这个Bug单看代码根本发现不了,因为脚本本身语法正确、逻辑完整,唯独缺少那条隐含的业务规则:这个系统里所有时间字段必须转换为本地时区再存储。

这个案例给我的启发是:AI不知道你的业务语境里有哪些“潜规则”。你如果把一条规则当作理所当然的常识,它就不会出现在提示词里,AI也就无从得知。所以防翻车的第一道防线,不是等代码生成后再去Review,而是在需求描述阶段就把你能想到的业务规则全部显式写出来。

2. 提示词设计:把翻车概率压到最低的关键操作

2.1 为什么提示词直接决定翻车率

很多人都以为提示词就是“把需求描述清楚”,这句话对,但太笼统了。我观察到的实际情况是:同样一个需求,不同人写给AI的提示词,生成的代码质量能差好几倍。差在哪儿?差在信息密度。

举个例子。你让AI“给我写一个登录接口”,它会生成什么?它大概率会生成一套最通用、最模板化的代码:硬编码账号密码、没有参数校验、没有异常处理、接口路径直接写死。为什么会这样?因为大模型是在“猜”你想要什么,而“登录接口”这个描述能匹配到的最常见训练样本,就是教学Demo里的简化版代码。它不是在偷懒,它是在最大化概率地猜你的意图。

反过来,如果把技术栈、数据库、token类型、有效期、错误码、验收标准、依赖版本全部写清楚,AI生成的代码质量会有明显提升。提示词里每多一条有效信息,就是帮AI把猜测范围缩小一圈,翻车概率也就跟着降一截。

2.2 差提示词的常见病:只写目标不写约束

我自己总结了差提示词的几类“常见病”,你们可以对号入座。

第一类:只写需求不写边界。比如“写一个文件上传接口”,但你不告诉AI文件大小限制、文件类型白名单、存储位置、是否需要异步处理,AI就会默认生成一个最简单的本地存储实现。第二类:只写功能不写技术栈。你心里想的是Node.js,AI可能给你生成Python Flask的版本。第三类:只写过程不写验收。你让AI“实现一个缓存模块”,但没说“请确保达到每分钟十万次读取的性能目标”,它生成的代码可能只满足功能不满足性能。第四类:一次让AI做太多事。在一个提示词里既让AI重构代码,又让AI写测试,还让AI写Git提交信息,它往往每件事都做不好。

这些病的本质是一样的:你只交代了“做什么”,没有交代“做到什么程度、在什么条件下做”。AI不是你的同事,它不会主动来问你这些信息。你不写,它就猜;它一猜,翻车就来了。

2.3 一个可以直接抄的提示词模板

下面这个模板是我在实际项目里反复打磨后固定下来的,基本可以覆盖日常后端开发的绝大多数场景。你们可以直接复制改一改再用。

【任务】 为模拟项目X实现登录接口 【需求背景】 这是一个跨平台应用,前端客户端调用,需要支持用户名密码登录,成功后返回JWT格式的token 【约束条件】 - 后端使用Python FastAPI框架 - 数据访问使用PostgreSQL + SQLAlchemy - token有效期设为2小时 - 密码必须使用哈希存储在数据库中,禁止明文 - 所有密钥从环境变量读取,禁止硬编码 【接口定义】 POST /api/v1/auth/login 请求体: {"username": "string", "password": "string"} 成功响应: {"access_token": "string", "token_type": "Bearer", "expires_in": 7200} 失败响应: 401 Unauthorized 【验收标准】 1. 用户名或密码错误时返回401 2. 请求参数缺少字段时返回422 3. 生成的代码可直接运行,依赖列表用requirements.txt单独给出 【输出要求】 直接输出可运行的Python代码,并在代码关键处添加简短中文注释。

每一块是什么作用,我拆开说一下。任务字段让AI知道自己在做什么;需求背景告诉它业务语境,尤其“跨平台应用”这种信息会影响它对接口形态的判断;约束条件是最关键的部分,直接圈死了技术选型和安全底线,防止AI自由发挥;接口定义省掉了AI猜字段名的过程,同时也方便你后面做联调;验收标准是给AI一个自检的尺子;输出要求保证代码能直接落地。

注意,这个模板不是让你一字一句照抄,而是让你体会“把约束写透”的价值。不同的任务,约束条件完全不一样。比如做前端页面,约束里就要写清楚UI框架、组件库、浏览器兼容性,甚至设计稿的间距规范。做数据脚本,就要写清楚时区、编码、失败重试策略。约束越多越细,AI越不容易跑偏。

2.4 上下文管理:别让AI“忘记”关键约定

提示词只解决第一次交互的问题。真正让很多开发者头疼的是长对话里的上下文丢失。你可能在一个会话里让AI写了好几个模块,聊了几十轮之后,再让它继续写代码时,它突然忘了最开始约定的“数据库是PostgreSQL”“接口前缀是/api/v1”。代码生成到一半,风格和之前完全不一致,甚至出现了两套互相矛盾的实现。

我自己处理这个问题有一个笨但有效的办法:按模块开新会话。每个新会话只聚焦一个子任务,然后把上一个会话里最重要的结论手动粘贴进来。比如写登录接口时,我在新会话开头粘一句“注意:本项目技术栈为FastAPI + PostgreSQL + SQLAlchemy,所有接口前缀为/api/v1”。这句话成本极低,但可以防止AI在几十轮对话中跑偏。

另外一个建议是:不要在同一个对话里频繁更换主题。上一轮让AI写登录接口,下一轮又让它写数据报表,再下一轮又让它帮你想朋友圈文案。主题一换,AI对前面上下文的权重就会降低,你回到编程话题时它大概率已经“失忆”了。我现在已经习惯把不同性质的任务拆到不同的对话窗口里,宁可多开几个会话,也不要在一条对话里杂糅。

3. 防翻车工作流:从需求到上线的四步实操

3.1 第一步:需求拆解成可验证的小模块

很多人翻车,不是AI的问题,是任务拆分的问题。一个功能模块如果太大,直接丢给AI,它很难兼顾全局和细节。比如“实现用户中心的完整后端”,这个任务里面包含鉴权、用户信息管理、头像上传、密码找回、操作日志,任何一个单独拿出来都是不小的工程。AI一口气生成几百行代码,你能保证逐行理解吗?出了问题你能快速定位吗?大概率不能。

我现在的做法是把需求拆成粒度更小的单元,每个单元能做到“独立验证”。以登录模块为例,至少可以拆成四块:数据库用户模型、密码哈希工具、JWT生成与校验工具、登录接口本体。每个小模块都是可以单独生成、单独跑测试、单独Review的。这样即使某个环节翻车了,影响面也是可控的,不会牵连整个功能。

拆分的粒度怎么判断?我的经验是:你拿到这段代码,能不能在五分钟内看懂每一行?如果不能,就说明拆得不够细。AI生成代码本身很快,但人类Review的速度是有上限的,拆得越细,Review越快,翻车的概率越低。

3.2 第二步:按依赖顺序分模块生成

模块拆完之后的生成顺序也有讲究。我建议按照依赖关系从底层往上层推进:先数据模型,再工具函数,再业务逻辑,最后是接口暴露层。原因很简单:底层模块是上层模块的基础,底层验证通过了,上层写起来才不容易出幺蛾子。

比如登录模块,我先生成用户表对应的SQLAlchemy模型,确认字段和约束没问题;然后生成密码哈希工具,单独写个脚本验证哈希结果可校验;再生成JWT工具,测试签发和解析是否正常;最后才让AI写登录接口逻辑,把前面几个模块组装起来。每一步都只做一件事,验证方式也简单:跑一个十几行的最小用例,确认结果符合预期。

这里有一个容易忽视的点:AI生成的代码之间可能互相不兼容。两个模块单独都能跑,合在一起却报错,最常见的原因是共享的变量名、类型定义、依赖版本不一致。所以在分模块生成时,最好在提示词里显式复用之前已经确认过的接口签名,比如“用户模型的字段名为username、password_hash,请基于这个定义继续实现”。

3.3 第三步:人工审查清单与兜底规则

AI生成的代码,不管看着多顺畅,合并代码之前必须过一遍人工审查。这是防翻车工作流里最关键、也最不能省略的一步。我自己的审查清单有五项,每个项目都踩过坑,写下来给大家参考。

第一,安全审查。检查有没有硬编码的密钥、Token、数据库连接串;有没有SQL拼接;有没有把敏感信息打印到日志里。AI默认是乐观的,它不会主动考虑安全问题,如果提示词里没提,它很可能给你一个“能跑但裸奔”的实现。第二,依赖审查。确认代码里用到的第三方库版本与当前技术栈一致,废弃接口有没有被误用。AI的知识有截止时间,它写出来的依赖版本可能已经老得被强制升级了。第三,异常路径审查。AI习惯于只写“主流程正常路径”的代码,超时、空值、重复请求、第三方服务不可用,这些大概率没覆盖。你需要在审查时逐个补上。第四,边界条件审查。时区、货币精度、分页的越界、并发下的状态覆盖,每一个边界条件都要过一遍。第五,性能审查。有没有在循环里查数据库?有没有对无索引字段做模糊查询?接口会不会在高并发下被打挂?

这五项审查完,AI生成代码的质量其实已经不低。但你要清楚,审查不是让你把每一行都重新读一遍,而是带着问题清单去扫,效率会高很多。

3.4 第四步:测试兜底与回归验证

最后一道防线是测试。AI写测试用例的速度非常快,让它生成单测骨架是一个很好的用法。但这里有个大坑:AI写的断言往往太乐观,它只验证“正确路径能跑通”,不验证“错误路径会失败”。比如登录接口的测试,AI会写“输入正确账号密码返回200”,但“连续输错5次触发锁定”这种业务规则,它大概率测不到。

所以我的做法是:让AI生成测试代码的框架和输入样例,但断言必须由我自己从需求文档里翻译过来。需求里写了“密码错误时返回401”,我就在AI生成的测试文件里,手动加一条断言:对错误的密码请求,断言状态码是401。这样AI负责写最快能铺开的测试覆盖,人类负责把需求里的验收标准映射到断言代码里。两者结合,测试才真正反映业务意图,而不是AI的幻觉。

测试跑通之后,还有一个回归验证的步骤:把生成的模块接到真实环境里跑一遍,用几条真实数据走一遍全流程,确认没破坏周边模块。这一步虽然简单,但能拦住很多集成阶段才会暴露的问题。

4. 真实翻车现场:问题复现与排查技巧

4.1 翻车类型速查表:五类高频问题一眼定位

日常工作中,AI生成代码翻车其实是有规律可循的。我把高频翻车类型整理成了一个速查表,排查问题时可以先对照这个表缩小范围。

翻车类型典型表现常见原因排查方向
语法正确但不工作报错提示找不到模块或属性AI幻觉生成不存在的接口看报错栈第一行,验证依赖版本
业务逻辑偏差结果不对、统计口径错上下文稀释导致规则遗忘逐条对照需求文档找差异
安全漏洞密钥硬编码、越权访问AI默认实现未加防护做代码安全扫描和人工审查
依赖版本问题用了废弃API或旧SDK训练数据知识陈旧查官方文档,核对版本差异
边界条件崩溃空值、超时、并发异常只写了主路径代码用边界输入做针对性测试

这个表是我自己排查问题的起点。遇到Bug,先判断它的表现属于哪一类,再直接跳到对应方向去查,比漫无目的地看代码快得多。比如“登录接口偶发报错”,如果报错信息是ModuleNotFoundError,几乎可以断定是幻觉或依赖版本问题,而不是业务逻辑问题,直接查import和依赖版本就行。

4.2 一个安全漏洞排查实录:AI生成的密钥硬编码

给大家完整展示一次排查过程。模拟项目X要做一个开放平台,我为它生成了API签名工具类。AI生成的代码里有这样一段:把项目的API密钥直接写在了常量里,还在构造函数里默认了一个备用的明文密钥。测试环境用的人少,这个问题一直没有暴露。

问题怎么发现的?不是代码Review,是接入方反馈签名一直校验不过。因为大家拿到的代码版本里都有同一个硬编码密钥,而生产环境我手动改成了另一套密钥,两边对不上,签名必然失败。顺着这个问题查上去,才发现整个团队所有成员的本地代码都在用同一个固定密钥。这意味着如果有人离职,这个密钥就得全量换,否则系统安全形同虚设。

这个Bug的根源在于:AI生成代码时,如果我们不在提示词里写“密钥从环境变量读取”,它就默认给你硬编码,因为在教学示例和开源项目的训练数据里,硬编码是太常见的写法。排查本身不难,静态扫描工具几秒钟就能查出来,难的是你愿不愿意在合并前跑这一步。现在我的流程里,但凡AI生成的代码要进生产分支,静态安全扫描是必选项,不扫不管合并。

4.3 让AI自己当审查者:一个高效的自检技巧

这里分享一个我自己试过且一直在用的技巧:让AI以“代码审查者”的身份重新检查它自己刚生成的代码。具体做法是在AI输出代码后,追加一条提示:“请以代码审查者的身份重新检查你刚生成的代码,列出潜在的安全问题、性能问题和逻辑漏洞,不要修改代码,只输出问题清单。”

实测下来效果不错。因为模型从“代码编写者”切换到“代码审查者”视角后,会从另一种角度审视同一段代码,确实能找出一些它自己生成时没注意到的问题。比如有一次它自己检查出了“未对分页参数做上限限制”,这是它在写代码时完全没有考虑的边界条件。

但这里要强调两点。第一,这个技巧不能替代人工审查,它能帮AI发现“通用问题”,但业务规则类的缺陷它依然发现不了。第二,不要让AI直接修改代码,我试过几次让它边审边改,结果它把原本正确的代码也改了,引入新问题的情况很常见。正确流程是:先让它输出问题清单,人看了之后,再决定哪些问题需要修、怎么修,修的时候还要回归一次原有功能。

4.4 个人避坑经验:三条用血和泪换来的习惯

聊了这么多,最后归纳三条我自己沉淀下来的避坑经验,全是实际操作中一步一步踩出来的。

第一条:不要直接复制AI输出的完整大文件。AI生成上百行代码后,先通读一遍再做任何操作。通读的时候重点看函数的输入输出、异常处理、外部依赖三个地方,这三处最容易藏雷。第二条:重要模块先让AI写一个“会失败的测试”,再让它写实现。让AI根据需求描述写一条预期会失败的测试用例,实际上是在验证AI是否理解了需求。如果它连正确断言都写不出来,那你凭啥相信它能写对实现?第三条:AI生成的代码必须单独开分支跑完整测试再合并。我见过有人直接在主分支上让AI改代码,改到一半报错,整个团队都被阻塞。分开分支,哪怕AI把代码改废了,也只是你一个人受影响,不会波及团队主干流程。

这三条习惯一开始做会觉得麻烦,但用久了就会明白:AI编程省下来的时间,如果不在审查和验证上花掉一部分,最后大概率会在返工和故障上加倍还回去。说到底,AI不会替你做工程决策,你的判断力才是这场效率革命的真正底座。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进