一个真实的故事:AI写的代码通过了CR,上线后炸了
上个月我让 Copilot 帮忙写一个 Redis 缓存的分布式锁。代码看着特别对——try-finally 包裹、expire 设置、异常处理一个不少。code review 两个同事都点了 approve。上线第三天凌晨,锁没释放,服务被卡死。
回头看那段代码,AI 把 SET lock_key value EX 30 NX 写成了 SET lock_key value NX EX 30——看似等价,但在特定 Redis 版本下,NX 和 EX 的顺序会影响语义。更坑的是:AI 写的单元测试用的是 mock,根本没连真实 Redis,所以全过了。
这个教训让我开始系统地思考一个问题:AI 生成的代码和「能上生产的代码」之间,到底差了什么?
接下来三个月我记录了 100+ 个 AI(Copilot + Cursor + ChatGPT)生成的代码片段在生产环境暴露的问题,总结了 5 道质量关卡。每道关卡都有具体的检查项和工具——不是方法论,是你今天就能用的东西。

第一关:Prompt 设计——让 AI 第一次就少犯错
多数人用 AI 写代码的方式是这样的:
# ❌ 模糊的 prompt
"帮我写一个处理 CSV 文件的函数"
AI 会给你一个能跑的版本,但你大概率不敢直接合并。问题不在 AI,在于你的需求没说清楚。把下面这些东西塞进 prompt,AI 生成的代码质量能直接提升一个档次:
高信息密度 Prompt 模板
你是一个资深 Python 后端工程师。请实现以下功能:
【需求】从 S3 读取 CSV 文件,解析后批量写入 PostgreSQL
【约束】
- 文件大小可能超过 2GB,不能一次性加载到内存
- 每条记录需要先验证字段完整性,非法行记录到单独的错误日志
- 使用 asyncpg 或 psycopg2 连接池,批量插入 1000 条/批
- 连接字符串和 S3 路径从环境变量读取
【输出要求】
- 完整的类实现,包含类型注解和 docstring
- 每段关键逻辑前用注释说明设计意图
- 列出至少 3 个可能的边界条件及处理方式
【不接受】
- 全局变量
- 裸 except
- 直接操作 sys.path
这个 prompt 和 “帮我写个处理 CSV 的函数” 的区别在哪?你给了 AI 一个「生产环境的上下文」。它知道文件可能 2GB(所以不会设计成 readlines() 全加载),知道数据库用连接池(不会每次建新连接),知道要处理脏数据。
实际测试:用模糊 prompt 生成的代码,我后续改了 4 处才敢合并;用高信息密度 prompt,改 1 处就够了。
Prompt 自查清单
| 维度 | 必填项 | 示例 |
|---|---|---|
| 技术栈 | 语言版本、关键库 | Python 3.12 + asyncpg |
| 性能约束 | 数据量、延迟要求 | 2GB 文件,P99 < 500ms |
| 错误处理 | 异常策略 | 记录错误行,不中断整体流程 |
| 边界条件 | 极端输入 | 空文件、编码错误、超长字段 |
| 代码规范 | 明确禁止项 | 不用裸 except、不设全局变量 |
| 输出格式 | 期望的结构 | 类实现 + 类型注解 + docstring |
第二关:静态分析——机器检查,零成本发现低级错误
AI 生成代码最常见的三类问题,90% 能被静态分析工具自动发现:
| 问题类型 | 示例 | 工具 |
|---|---|---|
| 未使用的导入/变量 | AI 生成了 import json 但代码里没用 | ruff, pylint |
| 类型错误 | 函数期望 List[str] 但 AI 传了 Optional[str] | mypy, pyright |
| 潜在的空引用 | dict.get() 返回值直接 .split() | mypy –strict, pyright |
我的工作流:AI 生成代码 → 保存到文件 → 立刻跑三件套:
# 我用 Makefile 封了一层,每次 AI 生成完代码自动触发
ai-code-check:
ruff check --select E,F,I,N,UP,B,SIM ai_generated/
mypy --strict ai_generated/
bandit -r ai_generated/ -ll
ruff 的杀手级用法:开启 SIM(简化规则)和 UP(升级规则)。AI 经常写出 Python 2 风格的代码或过度复杂的表达式,这两个规则能帮你自动发现并对大部分提供自动修复:
# AI 经常写这种冗余代码
if len(items) == 0: # SIM: 应该用 if not items
if x == True: # SIM: 应该用 if x
"{}".format(name) # UP: 应该用 f-string
# ruff --fix 一键修好
我们在团队里统计过:AI 生成代码后跑一次 ruff + mypy,能拦截约 67% 的低级错误(未使用变量、类型不匹配、潜在 None 引用、过度复杂逻辑)。每拦截一个,省一次 CR 讨论。
第三关:单元测试——让 AI 自证清白
这关思路很简单:让 AI 给 AI 写的代码写测试。
别笑——AI 写测试有个天然优势:它比你更了解自己生成的代码。你拿到 AI 生成的生产代码后,再用一个独立的 prompt(关键!不能复用上下文)让它生成测试:
你是测试工程师。以下是生产代码,请编写 pytest 测试:
【测试要求】
- 覆盖所有 public 方法
- 必须包含:正常输入、边界值、异常输入、并发场景
- 外部依赖(数据库/Redis/S3)用真实容器或 fakeredis/moto mock
- 不要只测 happy path——对这个函数而言,最危险的输入是什么?
【代码】
[粘贴 AI 生成的生产代码]
一个关键经验:测试的 prompt 必须和生产代码的 prompt 独立。如果你在同一段对话中让 AI “写完代码再写测试”,它写出的测试会迎合自己刚写的代码——跳过最危险的边界条件,因为它的认知里那已经是”对的”了。
还有一点:AI 写测试喜欢用 mock 过度隔离。下面这个是我在代码审查中最常抓到的反模式:
# ❌ AI 喜欢这样写——mock 了所有东西,什么都没测到
def test_lock_acquisition():
mock_redis = Mock()
mock_redis.set.return_value = True
lock = DistributedLock(mock_redis)
assert lock.acquire("key") == True
# "mock_redis.set 被调用了吗?"——测了个寂寞
# ✅ 应该连真实 Redis 或用 fakeredis
def test_lock_acquisition_with_real_redis():
import fakeredis
r = fakeredis.FakeRedis()
lock = DistributedLock(r)
assert lock.acquire("key") is True
assert lock.acquire("key") is False # 双重获取应失败
底线:AI 写的代码 + AI 独立写的测试 + 真实依赖(或高质量 fake)= 才算通过第三关。
第四关:人工审查——AI 代码特有的 5 个危险信号
前两关是机器的活。这一关必须人来。但人工审查 AI 代码和审查人类代码不完全一样——有些问题是 AI 代码特有的。我总结了五个危险信号,每次 review AI 代码时必查:
🔴 信号一:「看起来太完美」的代码
如果一段代码让你觉得”写得比我好”,停下来。AI 擅长生成语法优雅但逻辑有坑的代码。我见过最坑的一段:一个 async generator 写得层层嵌套、yield 时机精确、类型注解一个不落,但它把 async for 写成了普通 for——这在类型检查阶段根本不会报错,因为 Python 的 async generator 也实现了 __iter__,只是迭代结果不对。
🔴 信号二:注释与代码不一致
AI 经常改代码不改注释,或者注释是从训练数据里”抄”来的。如果看到一段注释描述了某个逻辑但代码明显不对——相信代码出了问题,不要相信注释。
# AI 生成的典型不一致:
# Check if the user has admin permission ← 注释说检查权限
if user.role == "admin" or user.id in owner_list: # ← 实际逻辑是 admin 或 owner
# ... 这两者权限等级完全不同!
🔴 信号三:错误的 API 调用或过时的库版本
AI 训练数据里混了多个版本的文档。它可能用 pandas 2.0 的语法写 pandas 1.x 的代码,把 inplace=True 用在已经 deprecated 的方法上,或者调用了压根不存在的参数。
验证方法:复制 AI 生成的每个函数调用,在项目 venv 里用 help() 或 inspect.signature() 确认参数签名。
🔴 信号四:不合理的默认值和硬编码
AI 喜欢给所有参数设置默认值,而且这些默认值经常是它”编”出来的:
# AI 编出来的 timeout=30 —— 为什么是 30 秒?数据库连接超时真的适合设 30 秒吗?
async def connect_to_db(dsn: str, pool_size: int = 10, timeout: int = 30):
...
🔴 信号五:缺少防御性编程
人类写生产代码会自然地加一堆防御:输入校验、重试逻辑、降级策略、监控埋点。AI 不会。它生成的代码在 happy path 下完美运行,但:
- 不会主动加
with suppress(TimeoutError)或不超时的重试 - 不会在关键路径打结构化日志
- 不会做 graceful degradation
- 不会考虑上游服务挂了怎么办
这些不是 AI 的 bug,是 AI 的 blind spot。审查时你得主动补上。
第五关:集成验证——在真实环境跑一遍
前四关过了,代码应该没问题了。但还有一个变量:AI 生成的代码和你已有的系统之间的交互。
举个例子:AI 写了一个高效的批量插入函数,用了 asyncpg.copy_records_to_table()。静态分析过了、测试也过了(用的是测试数据库)。上线后发现——生产环境 PostgreSQL 的 statement_timeout 设了 5 秒,而 10 万条记录的批量插入在 prod 的网络延迟下需要 8 秒。
这种问题在任何一个独立关卡都发现不了。只有把代码丢进真实环境(哪怕是 staging 环境)跑一遍才能暴露。
我的集成验证 checklist:
- 跑一次完整的端到端流程:不是单测,是真正触发整个业务流程
- 用生产级数据量:不是 10 条测试数据,是 10 万条
- 在 staging 环境而不是本地:网络延迟、超时配置、资源限制都和本地不同
- 观察至少 30 分钟:内存泄漏、连接泄漏这类问题不会在 10 秒内暴露
- 检查日志和 metrics:有没有异常的错误日志?有没有 CPU/内存异常尖峰?
五关全景:一套能复用的工作流
把五关串起来,就是一套完整的 AI 代码质量管控管线:
- Prompt 设计(5 分钟)→ 写一个高信息密度的 prompt,把约束说清楚
- 静态分析(30 秒)→ ruff + mypy 自动扫一遍,拦截 67% 的低级错误
- 独立测试(10 分钟)→ 用独立 prompt 让 AI 生成测试,连接真实依赖跑一遍
- 人工审查(15 分钟)→ 对照五个危险信号逐条检查
- 集成验证(30 分钟+)→ 丢进 staging 环境,用生产级数据跑完整流程
总耗时约 1 小时。相比「AI 写完直接合并然后半夜起来修线上 bug」,这笔时间花得太值了。
FAQ
Q: 五关全部做完是不是太慢了?AI 的价值不就是快吗?
第一关和第四关是你本来就应该做的(写清楚需求+review 代码),AI 并没有给你增加额外工作。第二关和第三关是自动化的(30 秒 + 10 分钟),成本极低。第五关是发布前本来就该做的集成测试。所以实际上 AI 省掉了你从零写代码的时间,而质量管控的成本并没有显著增加。反过来想:如果不用这五关,生产故障的成本是凌晨被叫起来修 bug + 业务损失——两小时起步。
Q: 小项目/个人项目也需要走完五关吗?
不需要全部。个人项目我建议至少做前两关(Prompt 设计 + 静态分析),成本几乎为零但拦截率很高。有用户数据的项目加上第四关(人工审查)。商业项目五关全走。
Q: ruff 和 mypy 的配置有推荐的起始模板吗?
有。在项目根目录放一个 pyproject.toml,ruff 开启 E/F/I/N/UP/B/SIM 规则,mypy 开启 strict 模式。完整配置参见本站的 Python 类型注解进阶实战 和 Python 性能剖析三件套。
Q: AI 写测试时 mock 和真实依赖怎么选择?
简单规则:外部服务用高质量 fake(如 fakeredis、moto for AWS),数据库用 testcontainers 启动真实容器。纯 mock(unittest.mock.Mock)只用于验证调用参数和次数,不用于验证业务逻辑。如果你发现测试中 80% 的代码是 mock 设置,那这个测试几乎没有价值。
总结
AI 编程工具不是银弹——它们是生产力放大器,但不是质量保证器。用好了,一小时能顶过去一天;用不好,凌晨三点查 bug 的就是你。
记住这五关不是为了增加流程负担,而是把 AI 代码的质量风险暴露在可控的时间和范围内(开发阶段),而不是暴露在不可控的时间和范围内(生产环境凌晨三点)。
今天就可以开始的三个动作:
- 把 ruff + mypy 加到你的项目 pre-commit hook 里
- 下次让 AI 写代码时,用本文的 Prompt 模板(把约束写清楚)
- AI 生成完代码后,对照五个危险信号过一遍
这三件事加起来不超过 20 分钟,但能拦住你能想到的大多数「AI 写的代码看着对但跑着炸」的场景。
📎 相关阅读:AI 辅助代码重构实战:用 Cursor 把 2173 行遗留代码重构成生产级 — 面对没人敢碰的遗留模块,怎么用 AI 安全地重构
免责声明:本文讨论的 AI 编程工具及方法论基于 2026 年 7 月的技术现状。AI 工具迭代迅速,部分 prompt 技巧和工具配置可能随时间变化。文中提到的具体工具版本和测试数据仅代表作者个人使用经验,不同团队和项目可能需要调整。
🔗 相关阅读:Python 性能翻车现场:10 个你以为很快但实际很慢的编码模式 — 从 list in 到 glob 遍历,每个坑都是生产环境的真实翻车经历。