AI 写代码为什么会错?上下文、测试和反馈循环
写在前面:AI 会写代码,但不会替你负责
现在用 AI 写代码已经很常见了。
你描述一个需求,它能生成组件;
你贴一段报错,它能给修复建议;
你让它写测试,它也能写得像模像样。
于是很多人会产生一个期待:
既然它会写代码,是不是可以直接交给它?现实是:可以让 AI 写,但不能让它独自负责。
AI 写代码会错,而且错得很有迷惑性。它不是完全不会写,而是经常写出“看起来合理、局部正确、放进项目就出问题”的代码。
这篇文章不讨论 AI 会不会取代程序员。我们只讲一个更实用的问题:
AI 写代码为什么会错? 怎么让它少错? 错了以后怎么建立反馈循环?错因一:它没有完整上下文
人类开发者写代码时,脑子里不只有当前文件。
你会知道:
项目用什么框架; 状态管理怎么做; 接口返回格式是什么; 错误处理习惯是什么; 哪些工具函数已经存在; 哪些历史坑不能碰; 团队代码风格是什么。AI 如果没看到这些,就只能根据通用经验猜。
比如你让它写一个登录接口,它可能生成:
直接查数据库; 自己拼 JWT; 返回 user 对象; 错误时抛 500。这段代码单独看好像能用,但你的项目可能已经有:
统一认证中间件; 统一响应 envelope; 统一错误码; 密码哈希工具; 审计日志; 限流策略; 多租户权限。AI 没看到,就不会自动遵守。
所以很多 AI 代码错误,本质不是“不会写”,而是“上下文不够,只能猜”。
怎么补上下文
不要只给一句需求。
更好的输入是:
相关文件; 现有接口示例; 错误日志; 测试用例; 约束条件; 不要改的边界; 期望输出格式; 项目已有模式。比如不要只说:
帮我加一个导出功能。可以说:
参考 UserExportService 的写法,给订单列表加 CSV 导出。 保持现有 API envelope,不改数据库结构。 权限走 requireAdmin,中间件已经在 routes/admin.ts。 先补测试,覆盖空结果、中文字段和无权限。这类 prompt 不只是“提示词更好”,它是在把工程上下文交给模型。
错因二:它会补不存在的东西
AI 很擅长根据模式生成代码。这个能力有用,也危险。
它可能会引用一个不存在的函数:
formatDateTime()可能会假设项目里有一个不存在的库:
import { createApiClient } from "@/lib/api"也可能会写一个框架旧版本的 API。
为什么?因为在训练数据里,这些写法很常见。模型看到你的需求后,生成了“看起来像这个项目会有”的代码。
但软件工程不是写作文。不存在就是不存在。
这类问题最好的解决方式不是和模型辩论,而是跑工具:
类型检查; lint; 单元测试; 构建; 运行时 smoke test。让真实反馈告诉它哪里错了。
错因三:它喜欢局部最优
AI 很容易修眼前错误。
测试报:
Cannot read property 'id' of undefined它可能加一行:
if (!user) return null;错误没了,但业务逻辑可能错了。也许这里不应该返回 null,而应该返回 401;也许上游根本不应该传空 user;也许这是权限绕过的信号。
AI 常常解决“报错文字”,而不是解决“系统问题”。
人类开发者要追问:
这个错误为什么出现; 输入边界是什么; 正确行为是什么; 有没有类似代码; 这个修复会不会吞掉错误; 测试有没有覆盖业务语义。AI 可以帮忙找方向,但不能替你做工程判断。
错因四:测试缺失
没有测试时,AI 写代码就像蒙眼开车。
它生成一段代码,你看着还行,但不知道:
边界条件对不对; 老功能有没有坏; 错误分支能不能跑; 权限有没有绕过; 输出格式有没有变; 性能有没有明显退化。AI 最喜欢在没有测试的项目里“看起来很高产”。它能快速改很多文件,但你没有反馈机制判断这些改动是否正确。
所以 AI 编程越多,越需要测试。
这里不是为了完成某个覆盖率 KPI,而是因为测试给 AI 提供了可执行反馈。
一个好的循环是:
写测试; 运行测试失败; 让 AI 根据失败实现; 再运行测试; 失败就把错误反馈给 AI; 通过后看 diff; 补边界测试; 再 review。错因五:反馈循环太长
AI 写代码的质量,很依赖反馈速度。
如果每次改完都要等半小时构建,或者没人跑测试,AI 就只能继续猜。
反馈循环越短,AI 越像一个能快速试错的助手。
反馈循环越长,AI 越像一个自信但不受约束的代码生成器。
理想状态是:
小步修改; 快速测试; 立即反馈; 只修一个问题; 再进入下一步。不要一次让 AI “重构整个系统”。除非你有非常强的测试和 review,否则这类任务很容易变成大面积不确定性。
错因六:它不理解你的隐性约定
项目里有很多东西不会写在 README。
比如:
这个字段历史上不能改名; 这个接口虽然难看但外部客户在用; 这个表不能加锁; 这个模块不允许引入新依赖; 这个错误码前端写死了; 这个函数慢一点没关系,但必须可回滚。这些隐性约定,AI 不可能凭空知道。
所以重要项目里,要把隐性约定尽量显性化:
测试; 注释; 架构文档; 编码规范; 类型约束; CI 检查; 代码审查清单。不是为了照顾 AI,而是为了照顾整个团队。
AI 只是把这些“工程卫生”问题放大了。
怎么让 AI 更可靠地写代码
我会按这个流程用 AI:
1. 先让它读,不急着写
先问:
请先阅读这些文件,总结现有实现方式,不要改代码。看它是否理解项目结构。理解错了,后面写代码大概率也会错。
2. 让它提出计划
尤其是多文件修改,先要计划:
需要改哪些文件; 每个文件改什么; 测试怎么补; 风险在哪里; 哪些地方不碰。计划不是形式主义,它能提前暴露误解。
3. 测试先行
能写测试的任务,先写测试。
测试会把需求变成可执行约束。AI 后面实现时,就有明确目标。
4. 小步修改
让 AI 一次只完成一块:
先补类型; 再补服务逻辑; 再接 API; 再补 UI; 最后整理错误处理。每一步都跑检查。
5. 必须看 diff
不要只看最终效果。一定要看它改了什么。
重点看:
有没有无关重构; 有没有删除重要逻辑; 有没有硬编码; 有没有吞掉错误; 有没有新增依赖; 有没有改变公开接口。6. 用工具验证,不靠口头保证
AI 说“已经修复”不算数。
算数的是:
测试通过; 类型检查通过; 构建通过; 关键路径手动验证; review 没发现高风险问题。AI 编程的正确心态
不要把 AI 当成“自动程序员”。更准确地说,它是一个很快、很博学、但没有项目责任感的协作者。
它适合:
生成草稿; 解释代码; 补测试; 查错误方向; 写样板代码; 整理重构计划; 做初步 review。它不适合独自负责:
安全边界; 架构决策; 生产数据操作; 复杂迁移; 无测试的大重构; 高风险业务逻辑。你可以让它开车,但方向盘、刹车和验收标准要在你手里。
按开发阶段看 AI 容易错在哪里
AI 写代码不是每个阶段都一样危险。
| 阶段 | AI 擅长什么 | 容易错什么 |
|---|---|---|
| 需求理解 | 总结、拆分、提问 | 忽略隐性约束 |
| 方案设计 | 给出候选方案 | 过度设计或套模板 |
| 写代码 | 生成样板和局部逻辑 | 引入不存在的 API |
| 写测试 | 覆盖常见路径 | 漏边界和业务语义 |
| 修 bug | 根据报错定位方向 | 只修表面现象 |
| 重构 | 提出整理思路 | 改动范围过大 |
| Review | 找明显问题 | 漏深层业务风险 |
所以不要对 AI 一刀切。
让它写样板代码,通常很值。
让它独自改核心架构,风险就高。
让它生成测试初稿可以,测试是否真正覆盖需求还得人看。
按代码类型分类
不同代码,AI 的可靠性也不同。
1. 样板代码
比如:
表单组件; DTO; 路由注册; 简单 CRUD; 配置示例。AI 很擅长。只要项目模式清楚,它能省很多时间。
2. 胶水代码
比如:
调用第三方 API; 文件格式转换; 脚本自动化; 数据同步。AI 也比较适合,但要注意版本和错误处理。它经常写出过期 SDK 用法。
3. 业务核心代码
比如:
计费; 权限; 订单状态机; 审批流; 风控规则。这类代码 AI 可以辅助,但不能靠它猜。因为关键规则往往不在代码表面,而在业务约定里。
4. 并发和性能代码
比如:
锁; 缓存一致性; 异步队列; 批处理; 数据库事务。AI 很容易给出看似合理但有隐藏问题的方案。必须通过压测、事务分析和代码 review。
5. 安全相关代码
比如:
认证; 授权; 加密; 签名; 输入校验; 权限边界。这类代码要非常谨慎。AI 可以生成参考,但最终必须按安全标准检查。
按项目成熟度分类
AI 在不同项目里的表现也不一样。
新项目
新项目没有太多历史包袱,AI 发挥空间大。
它适合:
搭目录结构; 写初版组件; 生成测试框架; 创建 API 草稿; 快速做 demo。风险是过度生成。它会把一个小项目写成大平台。
成熟项目
成熟项目有大量隐性约定。
AI 如果没读上下文,很容易:
破坏兼容性; 绕开已有工具; 重复造轮子; 改掉历史逻辑; 引入不符合团队规范的依赖。成熟项目里,AI 最好先做阅读和局部修改,不要一上来大重构。
遗留项目
遗留项目文档少、测试少、风格乱。
AI 会更容易猜错,因为它看到的模式本身就不稳定。
这类项目更适合让 AI:
补注释; 画调用关系; 生成 characterization tests; 整理风险点; 小范围提取函数。不要让它直接“全面现代化”。
验证也要分层
不是所有改动都需要同样重的验证,但至少要有梯度。
| 验证层级 | 适合改动 |
|---|---|
| 语法检查 | 小脚本、配置、简单函数 |
| 类型检查 | TypeScript、Java、Go 等强类型项目 |
| 单元测试 | 纯函数、业务规则、工具函数 |
| 集成测试 | API、数据库、队列、外部服务 |
| E2E 测试 | 用户关键路径 |
| 手动验收 | UI、交互、复杂业务 |
| 安全 review | 权限、输入、数据、密钥 |
AI 改代码后,至少要跑和风险匹配的验证。
比如改按钮文案,可能不需要完整 E2E。
改登录权限,只跑格式化肯定不够。
更好的 AI 编程任务写法
把需求写给 AI 时,可以按这个模板:
目标:要实现什么; 上下文:相关文件和已有模式; 边界:哪些文件不要改; 约束:响应格式、权限、性能、安全要求; 测试:先补哪些测试; 验收:什么命令通过才算完成; 风险:哪些地方需要特别注意。比如:
目标:给订单列表增加 CSV 导出。 上下文:参考 UserExportService 和 admin routes。 边界:不要改数据库结构,不要改现有分页接口。 约束:只允许 admin,中文字段要正确编码。 测试:空结果、无权限、含逗号字段、中文字段。 验收:npm test 和 npm run build 通过。 风险:不要把普通用户也暴露导出入口。这种任务描述比“帮我加导出”强很多。
AI 代码 review 也要会用
AI 不只适合写代码,也适合做第一轮 review。
可以让它检查:
是否有无关改动; 是否重复造轮子; 是否漏错误处理; 是否有空值问题; 是否违反现有模式; 是否需要补测试; 是否有明显安全风险。但 AI review 也不能代替人。它容易漏:
业务语义; 历史兼容; 组织约定; 真实用户影响; 性能瓶颈; 安全边界里的细节。最好的方式是让 AI 做第一层机械检查,人做最终判断。
最终结论
AI 写代码会错,主要不是因为它“笨”,而是因为软件工程本来就依赖上下文、约束和反馈。
最常见的错因是:
上下文不完整; 补了不存在的函数或库; 只修表面错误; 缺少测试; 反馈循环太长; 不了解项目隐性约定。让 AI 少错的办法也很明确:
给足上下文; 先计划; 测试先行; 小步修改; 跑工具验证; 认真看 diff; 保留人工 review。一句话:
AI 编程的关键不是让模型一次写对,而是建立一个它写错了也能被快速发现、快速纠正的工程循环。