AI生成代码这件事,圈子里早就不是什么新鲜话题了。但聊得越多,我越发现一个尴尬的现实:大多数人拿到的代码,跑个demo没问题,一放进真实项目就崩,或者风格跟现有代码完全不在一个频道上,review起来比从头写还痛苦。这不是模型能力不行,而是大多数人的提问方式还停留在"给我写个XX"的阶段。
这几个月我花了大量时间在一件事上:怎么让AI产出"即用型代码"——不是能运行,而是可以直接进代码库、过Code Review、上线不心虚的那种。今天把三条摸索出来的核心技巧摊开聊,都是踩坑换来的经验,希望能帮你省掉那几个月的时间。
1. 为什么AI写出的代码"能跑"却"不敢用"
先聊聊问题出在哪。很多人对AI编程的预期是"一句话换一段能跑的代码",这个预期本身就是错的。能跑和能用之间隔着一整条工程鸿沟:异常处理、边界条件、依赖管理、性能表现、风格兼容、可测试性——这些才是代码进生产环境的门槛。
我给一个非常具体的例子。某次我需要一个批量处理函数,简单描述给AI,它很快给了段能跑的Python代码,逻辑看着没问题。但放进真实模块时发现三个致命伤:
- 没有处理
generator在部分成功时的资源泄漏——异常抛出后文件句柄没关。 - 大批量文件时用单一内存缓存,跑一批两万条数据直接
MemoryError。 - 函数内直接
print输出,跟现有模块的日志规范完全不兼容。
这三个问题在demo环境全部发现不了,只有进入真实项目才会暴露。更麻烦的是,这些问题不是模型"笨",而是提问时压根没给它相关约束。模型不会主动猜测你的工程背景,你给它多少上下文,它就只能在这个范围内做最优决策。
另一个同样常被忽视的因素是"对话污染"。很多人把AI当聊天机器人,先聊五分钟天气再问代码,或者在一个对话里问十几个不相关的问题。模型在处理这种对话时会逐渐模糊你的真实场景,生成的代码越来越"模板化"。我测试过同一需求在不同对话长度下的生成质量,明显感觉到,干净、聚焦的对话上下文里产出的代码,工程质量远高于被垃圾信息包围的对话。
所以结论很明确:想让AI产出即用型代码,先得把它的"工作现场"搭好。这就是第一个技巧的核心逻辑。
2. 技巧一:现场重建式上下文——给AI看代码库,而不是讲故事
绝大多数人给AI描述需求时,习惯用"讲故事"的方式:我们系统有个模块,需求是这样那样的,你能不能写个函数处理一下。这种描述里信息密度极低,模型收到的是一堆形容词,而不是精确的边界条件。
我管正确做法叫"现场重建":不描述你要什么东西,而是直接把相关工作现场中的关键切片喂给AI。具体有三个切片是最高效的。
2.1 技术栈与版本文档:锁定语言和框架边界
先让AI明确它是在为哪个技术栈写代码。这不是指说一句"用Python",而是要精确到版本和框架。举个例子,我一般在提示词开头塞一段这样的东西:
工作上下文:Python 3.11 + FastAPI 0.100 + SQLAlchemy 2.0。项目使用asyncio异步风格,数据库操作全部走async session。禁止使用同步数据库驱动。现有代码遵循PEP 8,但类型标注强制使用from __future__ import annotations。 需求:为现有订单模块实现一个分页查询接口。这段信息看着简单,但对生成结果的影响是决定性的。同一个需求,"写个分页查询"和"按上面约束写个分页查询"产出的代码差了不止一个档次:一个会给你同步风格的查询然后丢进async函数,另一个会主动用select().offset().limit()+ScalarResult来保持风格一致。
实测下来,很多你觉得"AI不懂异步""AI乱用全局变量"的抱怨,其实是因为没告诉它项目本身就是异步的,也没告诉它项目规范禁止全局状态。模型确实会默认选最通用的方案,而"最通用"往往等于"和你项目不搭"。
2.2 同模块既有代码:风格对齐的最短路径
比任何文字说明都管用的,是直接给AI一段你要仿写的代码片段。风格这种东西,描述半天不如给它看一眼。
比如拿到需求之后,我会从目标模块里挑一到两个类似功能的函数,连同它的import、docstring、命名习惯一起贴进去,跟AI说"按这个风格写"。这样生成的代码在函数命名、返回方式、异常处理模式上都会向既有代码靠拢,Review时几乎不需要改格式。
这招对"一个代码库多个作者风格混用"的老项目尤其好用。先把目标区域的那个文件读进上下文,让AI明确"此文件当前风格",它产出的新代码就会主动适配,而不是另起一套。我在某个跨平台系统里试过,用这种方式补一个新接口,最终PR的diff小到令人感动。
2.3 已有约束描述:把隐性规则显性化
每个代码库里都有一堆没写进文档的"潜规则":错误码怎么组织、日志用什么级别、配置项放哪里、依赖怎么引入。这些对老手来说是常识,但对AI来说是盲区。你要把这些规则显性化,写进上下文里。
这里有个很实用的策略——用"关键词锚点"代替长篇解释。别写"我们要确保系统稳定高效"这种空话,改成:
约束:所有外部调用必须设置超时时间,默认5s;错误统一抛AppError并携带错误码;日志用logger = logging.getLogger(__name__)创建模块级logger;数据库连接只允许在Repository层使用。这四条一说,AI生成的代码基本就符合老项目的手感了。如果漏掉这些,它大概率会写出裸requests.get()、裸logger = logging.getLogger(__name__)在函数里重复创建之类的"教科书错误"。
我把上下文写法整理成一个可复用的模板,效率从"聊半小时需求"压缩到"贴三块信息":
[技术栈与版本] 这里是语言/框架/版本/关键库的精确声明 [同模块风格参考] 这是一段你希望我遵循的现有代码 [工程约束] 这是所有新增代码必须遵守的规则列表 需求:<你的具体需求>这套模板不一定适合所有场景,但对于80%的CRUD接口、数据批处理、CLI工具和脚本,它比任何长篇对话式描述都好用。
3. 技巧二:用"约束锚点"把需求写成工程清单
上下文到位之后,下一步是"需求本身的高精度表达"。大多数人的需求描述是"帮我写个导出功能",这等于没写。AI只能猜你要CSV还是Excel、要不要异步、要不要进度反馈,猜错的概率很大。
我把一套能显著提升成果质量的方式叫"约束锚点法":在需求陈述里主动嵌入若干约束维度,让模型在生成时围着这些维度做多目标优化。就像在多智能体系统里,每个智能体都要在两个目标间权衡,所以你先得明确目标权重;同样,不给约束,模型就只能自己猜。
3.1 十二个可用的约束维度
我总结了一下,日常需求里常见且实用的约束维度有这么几类:
| 维度 | 说明 | 示例表达 |
|---|---|---|
| 边界与异常 | 输入边界如何定义,异常如何处理 | "输入列表可能为空,需返回空结果而非抛错" |
| 性能基线 | 时间与空间要求 | "单条处理不超过100ms,内存波动不超过256MB" |
| 安全约束 | 哪些操作被禁止,哪些必须检查 | "不得直接拼接SQL,必须走参数化查询" |
| 可观测性 | 日志、指标、追踪要求 | "每个关键步骤需输出结构化日志,包含耗时" |
| 兼容范围 | 要兼容的版本或平台 | "需支持Windows和Linux双平台路径分隔" |
| 依赖策略 | 允许引入哪些依赖,禁止哪些 | "只允许使用标准库和已有依赖,不得新增" |
| 编码风格 | 命名、组织、注释要求 | "函数必须遵循单一职责,行宽不超过88字符" |
| 测试要求 | 是否需要可测试结构 | "逻辑应与IO解耦,方便mock" |
| 文档同步 | 是否需要更新注释或README | "需同步更新模块顶部docstring" |
| 迁移影响 | 是否影响既有代码 | "不得改动公共接口签名" |
| 部署考量 | 运行环境与启动方式 | "需兼容Docker容器内环境变量注入" |
| 维护性 | 后续扩展友好性 | "配置项通过参数注入,不硬编码" |
你不用每次塞满十二个,根据项目阶段选五到八个最相关的锚点就够了。关键是每条都要给"可判定的标准"——让AI生成完自己能检查的那种,而不是"请写好一点"这种无法衡量的废话。
3.2 一个直接的对比:有锚点和没锚点的差距
看两个提示词的差异。第一个是大多数人会写的:
帮我写一个从CSV导入用户数据的函数第二个是加了锚点的版本:
从CSV导入用户数据到数据库。要求: 1. 支持空文件和不完整行,不完整行跳过并记录到error_log 2. 单批最多处理5万条,超过则分批写入 3. 所有数据库操作走参数化SQL,禁止拼接字符串 4. 函数结构需与Repository层解耦,便于单独测试 5. 新增依赖需明确注明理由,否则尽量用标准库实现 6. 对重复用户按email去重,保留最新记录实测下来,第一个提示词生成的代码大概率会在异常处理、性能、安全三处出问题;第二个版本基本是一次过Review。原因不复杂——模型在生成时也是在做一个隐式的多目标优化,你给它更多明确的约束锚点,它的优化方向就更接近你的工程预期。
3.3 约束锚点的"优先级标注"技巧
锚点不是越多越好。如果十几条约束优先级平齐,模型反而会顾此失彼。我的做法是给约束分权重,用简单的标记提示模型:
需求实现优先级: [P0] 任何情况下不得抛裸Exception,必须包装为AppError [P1] 大数据量时优先考虑流式处理 [P2] 代码注释可以少一些,但命名必须自说明P0是硬性规定,P1和P2是软性要求。模型会优先保证P0,再考虑P1和P2。这样比"所有要求一律重要"的效果好很多,你可以试几次感受差异。
4. 技巧三:红队验收——让AI自己拆穿自己生成的代码
代码生成出来,事情只完成了一半。很多人把"生成"当终点,其实"验收"才是决定代码能否落地的关键。第三个技巧的出发点很反直觉:拿到AI的代码后,不要急着编译和测试,先让AI自己拆穿自己。
我把它叫"红队验收"——模仿安全领域的红队做法,把生成的代码当成"敌人提交的代码",用质疑的态度做四道防线检查。而且这几道检查最好让AI自己执行,因为它最清楚自己写的代码的薄弱点在哪。
4.1 第一道:边界条件挑战
直接追问模型:
这段代码在哪些输入下可能出问题?请列出5种会导致异常、死循环或数据错误的场景。让它列场景,目的不是真的要看这些场景,而是逼它重新审视自己写的代码。这一步最常见的收获是发现"列表为空时索引越界""文件句柄在异常时未关闭""时间边界判断错误"这类低级问题。模型这时候往往会主动承认并给出修复版,比你拿着代码逐行检查快得多。
4.2 第二道:资源与性能审计
接着问资源占用和性能:
这个实现的最大瓶颈在哪?如果输入量扩大100倍,哪个环节会先扛不住?需要改动哪些部分才能支撑这个量级?这招对数据处理类的代码特别管用。我有次让AI写日志解析器,它自己审完承认了内存占用呈线性增长,还主动给了用mmap改写的方案。这种对话式的"性能压力测试"效率极高,因为模型对自己生成代码的性能特征其实有隐性知识,只是没人问它。
4.3 第三道:安全与异常路径审计
再问安全性,重点是让AI对比"乐观路径"和"悲观路径"的执行差异。例如:
如果数据库连接池耗尽、认证过期或权限不足,这段代码各自的失败表现是什么?会不会把敏感信息泄露到日志里?这一问能挖出不少隐藏问题。常见的有:代码把SQL参数原样打印进日志、异常把堆栈细节直接返回给客户端、没有对用户输入长度做限制。这些问题如果在集成阶段才被发现,修起来成本就高了。
4.4 第四道:测试样例生成
最后一招,让AI生成测试用例:
给这段代码写一份覆盖边界、错误路径和正常路径的最小测试集。之前很多人在让AI写测试时有个误区——让AI"多写测试",它就会生成一堆覆盖正常路径的用例,看着很多但价值有限。改成"按边界、错误、正常三个分类各写几条",效率会高得多。这个要求本身就是一种约束锚点,把测试维度也嵌进去了。
我一直强调"人要在循环里",AI的自审不是让你完全撒手。自审结果能帮你快速定位风险区域,但真正要合并代码前,你还是得明白每段关键路径的逻辑,至少要能说清楚"为什么要这么处理"。把生成、自审、人工确认这三步串起来,才是完整的闭环。
5. 三条提示词流水线的组合打法与边界
三个技巧单独用都有效,但把串起来形成一个流水线,效果才最强。这套流程我在日常开发中已经跑顺了,分四步,每步都对应上面说的技巧:
- 搭现场:贴技术栈版本 + 同模块样例代码 + 工程约束,让AI进入"项目模式"。
- 下需求:用约束锚点把需求写成五到八条可判定标准的工程清单,标注P0/P1/P2优先级。
- 生成与自审:让AI生成初版代码,然后立刻用红队验收四连问做自审,把发现的问题直接让它在同一轮对话里修完。
- 人工确认:重点检查P0约束是否真的实现,关键函数是否理解无偏差,然后才进Review流程。
这套流程跑下来,最大的变化不是AI生成的代码一次就对(那种情况极少),而是"从生成到合并"的迭代次数大幅减少。以前可能要来回改七八轮,现在基本三轮以内能定稿,而且代码风格、异常处理、安全性都贴合项目规范,Review成本降了不少。
不过这三个技巧不是万能的。有几个边界情况我自己也踩过,顺便说清楚:
第一,如果需求本身就是模糊的、你也不清楚边界条件,任何提示词技巧都救不了。AI不会替你完成需求分析,它只能在需求明确的前提下做工程化表达。先花时间把需求想清楚,再谈提示词。
第二,项目上下文太多时,"现场重建"的成本会变高。试过给模型一次性塞十几个文件,结果上下文窗口撑爆,模型开始丢失早期信息。现在遇到跨多模块的大型改动,我会压缩上下文、一次只让AI处理一个模块,或者先让它帮我把模块间接口抽象出来,分别生成再组装。
第三,涉及敏感数据和核心安全逻辑的场景,AI自审的"表面正确"不能替代独立的安全评审。提示词能降低低级错误概率,但涉及权限模型、支付逻辑、加密方案这类内容,必须走真人设计评审和代码审计,别偷懒。
我自己的体会是:这套方法论的核心价值在于改变了提问的思维方式——从"让AI告诉我答案"变成了"给AI一个我认可的工程环境让它帮我完成实现"。这两个方向带来的代码质量差异,比任何单条"神奇提示词"都大得多。
最后再分享两个顺手的小习惯。一个是版本管理提示词本身,把每次跑通的高质量提示词存档,跟代码放一起,后面同类需求直接复用或微调,不要每次重新想。另一个是善用"如果换个实现方式会怎样"这个提问——很多项目方案不是只有一个解,让AI快速给出两三种思路再选,往往能省掉重构的时间。这些东西坚持用一段时间,你会明显感受到AI在生产代码这件事上的角色,从"玩具"变成了"值得托付的结对编程伙伴"。