第四章:为什么 AI 写代码经常翻车


翻车,是 AI 的问题,还是你的问题?

如果你已经用过 AI 写代码,那你一定见过这些场景:

  • 越改越乱,改一个地方出两个问题
  • 修了一个 bug,又冒出两个新 bug
  • 同样的代码重复生成三遍
  • 它根本不懂你的业务逻辑
  • 该改 A 文件,它去改了 B 文件
  • 原本还算干净的结构,被它改得像装修现场

很多人会把这些问题归因于"模型还不够强"。这不算错,但不够本质。

更根本的原因通常是:你给的信息不够好。

这章我们来彻底拆解六大翻车场景,每一个都给出系统性解决方案。


翻车场景一:越改越乱——目标不清

现象描述

你说"帮我优化一下这个页面",AI 根本不知道你说的优化到底是:

  • 性能优化
  • 视觉优化
  • 代码结构优化
  • 交互优化
  • 可访问性优化

于是它会凭感觉动手,最大概率改了一堆你不在乎的地方。

实际案例

你有一个商品详情页,加载有点慢。你说:

帮我优化这个商品详情页

AI 可能:

  • 重写了 CSS,引入了 CSS-in-JS(你不想引入新依赖)
  • 把你的 class 组件改成 hooks(你知道,但这次不想动结构)
  • 给每个图片加了懒加载(对,这个你需要)
  • 重构了状态管理逻辑(完全不需要)

结果是:你真正需要的懒加载做了,但同时引入了三堆你不想要的改动,diff 里全是噪音。

解决方案:明确目标,只做一件事

请只为商品详情页的图片部分做性能优化。

目标:
- 给所有商品图片加懒加载

约束:
- 只修改 ProductDetail.tsx 中的图片相关代码
- 不修改样式文件
- 不修改状态管理
- 不引入新依赖

请先说明你准备改哪几行,再输出代码

翻车场景二:修 bug 修出新 bug——上下文不完整

现象描述

你只给了一个报错截图,但没给:

  • 触发步骤
  • 预期行为
  • 实际行为
  • 相关代码文件
  • 最近改动历史

AI 只能瞎猜。它一旦猜错,就容易做出"局部修复、整体破坏"的事情。

实际案例

你的接口返回 500 错误。你给 AI 看了报错信息:

TypeError: Cannot read properties of undefined (reading 'id')

AI 没有看你完整的处理逻辑,它猜测是某个地方没有做 null 判断,于是把三个地方都加上了 ?. 可选链。

但实际上问题是你在调用接口前没等异步操作完成,数据确实是 undefined。AI 的修法只是把错误藏起来了,不是真正修复了。

解决方案:给够"诊断所需的信息"

修 bug 的 Prompt 模板:

请帮我分析并修复这个 bug。

【报错信息】
TypeError: Cannot read properties of undefined (reading 'id')
  at OrderCard.tsx:45

【触发步骤】
1. 进入订单列表页
2. 点击任意一个订单卡片
3. 跳转到订单详情页时报错

【预期行为】
正常跳转到订单详情页,显示订单信息

【实际行为】
页面白屏,控制台报上述错误

【相关代码】
[贴 OrderCard.tsx 第 40-55 行]
[贴 OrderDetailPage.tsx 的关键部分]

【最近改动】
我刚刚把 router 升级了 v5 → v6,改了路由参数的获取方式

请先分析 bug 原因(可能有多个假设),再给出修复方案

翻车场景三:重复生成——没有建立唯一事实源

现象描述

很多人一会儿在 ChatGPT 里问,一会儿在 IDE 里改,一会儿又手动复制粘贴。最后同一个功能出现三版实现:

  • 一版写在页面里
  • 一版写成 hook
  • 一版又封装到 util

不是 AI 故意重复,而是系统里根本没有明确告诉它:“当前唯一有效实现在哪。”

实际案例

你让 AI 写了一个日期格式化函数,然后在不同地方问了不同问题,AI 在三个地方都内联了这个函数,没有复用。

或者更严重的情况:AI 看到你的代码里有三种不同的日期格式化方式,它随机选了一种继续用,导致代码里到处都是不一致的日期格式。

解决方案:明确告诉 AI"真相在哪"

注意:这个项目有一套标准工具函数,在 src/utils/format.ts

日期格式化统一使用 formatDate(date, 'YYYY-MM-DD') 这个函数,
不要自己实现日期格式化,也不要引入其他日期库

现在请帮我实现订单列表的显示,日期格式化用上面说的统一函数

翻车场景四:不理解业务逻辑——只给了"功能",没给"规则"

现象描述

比如你说:做一个订单取消功能

对人类开发者来说,这里最关键的不是按钮,而是规则:

  • 已支付能不能取消?
  • 已发货能不能取消?
  • 谁能取消?
  • 取消后库存怎么回滚?
  • 优惠券要不要返还?
  • 取消申请是直接取消,还是要审批?

如果这些规则没说,AI 只能生成"看起来像取消功能"的东西——有按钮,能点,但业务逻辑全错。

解决方案:把业务规则写进 Prompt

请实现订单取消功能。

业务规则(这很重要,请严格按照这些规则实现):

状态限制:
- 状态为"待支付":可以直接取消
- 状态为"已支付,待发货":可以取消,需要发起退款流程
- 状态为"已发货":不可取消,只能申请退货
- 状态为"已完成":不可取消

权限限制:
- 用户本人可以取消自己的订单
- 管理员可以取消任何订单
- 普通员工无取消权限

取消后的逻辑:
- 库存回滚(调用库存服务的 restoreStock 接口)
- 如果使用了优惠券,返还优惠券
- 发送取消通知邮件
- 记录取消原因(required 字段)

技术约束:
- 取消操作必须是事务(库存回滚和状态更新要原子)
- 使用已有的 NotificationService

翻车场景五:修改错误文件——没给边界

现象描述

这在大项目里非常常见。你想让它改一个表单校验,它可能顺手:

  • 改组件
  • 改接口
  • 改类型定义
  • 改公共工具函数
  • 顺便把 unrelated 的命名也统一了

从它的角度看,这是"积极主动"。从你的角度看,这是灾难。

为什么会这样?

AI 没有"只改必要的东西"的内置冲动。它的默认行为是"把能做的都做好",这听起来不错,但在工程实践里这是大问题:

  • 每次改动范围超出预期,code review 负担加重
  • 顺手改的地方可能引入 bug
  • 你失去了对修改内容的控制感

解决方案:用文件白名单严格限制

请注意:这次修改只允许动这两个文件:

1. src/components/LoginForm.tsx
2. src/validation/loginSchema.ts

不允许修改的文件:
- 任何 API 相关文件
- 任何全局状态文件
- 任何类型定义文件
- 任何工具函数文件

如果你认为需要改其他文件,请先说明原因,等我确认后再改

翻车场景六:架构污染——没有持续约束

现象描述

AI 天生偏向"先把事情做成",而不是"长期维护最优"。

如果你不给它结构规则,它很容易:

  • 把业务逻辑塞进组件
  • 把 SQL 字符串写在接口层
  • 把常量写死在代码里
  • 把临时补丁堆成长久实现
  • 在已经有封装的地方重新造轮子

短期看能跑,长期看全是债。

实际案例:一个"登录页小改动"是怎么滚成事故的

假设你本来只想做三件事:

  • 登录按钮 loading
  • 登录失败提示更清楚
  • 记住我复选框默认勾选

结果你给 AI 的指令是:帮我优化一下登录页体验

AI 可能会:

  • 重写整个表单(因为它认为现有表单"有改进空间")
  • 替换原来的状态管理方式(因为它更喜欢用 Zustand)
  • 顺手引入一个新 UI 组件(因为它觉得好看)
  • 改了校验逻辑(因为它认为旧写法不够现代)
  • 改了 API 请求封装(因为它想"顺手"统一一下)

最后一个本来半小时能改完的需求,变成一场 diff 灾难。

解决方案:设定架构规则,并持续重申

方法一:在 Prompt 中明确架构约束

请注意这个项目的架构规则,请严格遵守:

1. 所有 API 请求必须通过 src/api 目录下的函数,不要在组件里直接 fetch
2. 全局状态使用 Zustand store,不要新建本地 context
3. 表单校验统一使用 react-hook-form + zod,不要换其他方案
4. UI 组件只能用项目已有的 shadcn/ui 组件,不要引入新组件库
5. 类型定义统一在 src/types 目录,不要在组件文件里随意定义

这次只修改登录按钮的 loading 状态,其他什么都不要动

方法二:建立一个 RULES.md 文件,每次都带上

在项目根目录建立 RULES.md,写清楚所有架构规则,每次给 AI 任务时都附上这个文件的内容。


系统性预防翻车的五要素

给 AI 的信息至少要包含五类:

要素 说明 示例
目标 你到底要什么(只做一件事) “给登录按钮加 loading 状态”
边界 哪些文件能改,哪些不能改 “只修改 LoginPage.tsx”
约束 技术栈、风格、架构规则 “不要引入新依赖,不要修改接口层”
验收 什么叫完成 “按钮 loading 时变灰不可点,完成后恢复”
风险 哪些地方别乱动 “不要动表单校验逻辑,那部分有测试”

完整 Prompt 示例:正确的"改登录页"

请只对登录页做小范围修改,不要重构。

目标:
1. 登录按钮在提交时显示 loading
2. 登录失败时显示后端返回的错误信息(目前是固定文案)
3. "记住我"复选框默认勾选

约束:
1. 只能修改 LoginPage.tsx
2. 不允许修改接口层(src/api/auth.ts)和全局状态(src/store/userStore.ts)
3. 保持现有表单库(react-hook-form)不变
4. 不要新增依赖

验收标准:
1. 点击登录后,按钮变灰 + 显示 loading 图标,完成后恢复
2. 后端返回错误时,在表单下方显示具体错误文字
3. 页面首次加载时,记住我已经勾选

风险提示:
不要动 handleSubmit 的调用方式,那里有一些边界处理,动了可能出问题

请先说明你准备改哪些地方,再给出代码

一句话总结

AI 写代码经常翻车,不是因为它只会乱来,而是因为你没把路标、护栏和终点线立起来。


上一章:第三章 — Token 是什么
下一章:第五章 — Prompt 才是新时代程序员的核心技能

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐