Vibe Coding 踩坑实录:10个让 AI 编程翻车的常见错误

Vibe Coding 是 2025 年 Karpathy 提出的 AI 编程范式,极大提升了开发效率。本文分享了 10 个最常见的翻车场景,每个都是实战中踩过的坑,配上可操作的预防措施,帮你在享受 Vibe Coding 效率红利的同时,守住质量底线。

Vibe Coding 是 2025 年 Karpathy 提出的 AI 编程范式:用自然语言描述需求,AI 生成代码,你觉得"感觉对了"就直接用。这种方式极大提升了开发效率,但如果不知道边界在哪里,翻车只是时间问题。

过去半年,我见证了太多"Vibe Coding 翻车"的惨痛案例:开发环境跑得好好的,一上线就崩溃;AI 生成的代码华丽流畅,但安全漏洞百出。这些问题都指向同一个核心矛盾——效率红利与质量控制的冲突

今天我要分享 10 个最常见的翻车场景,每个都是实战中踩过的坑,配上可操作的预防措施,帮你在享受 Vibe Coding 效率红利的同时,守住质量底线。


场景1:Promise.all 没有错误处理

问题:AI 生成的并发请求代码用了 Promise.all,但单个接口失败会导致整个请求链路崩溃。

翻车现场

1
2
3
4
5
6
// AI 生成的代码
const results = await Promise.all([
  fetchUser(id),
  fetchOrders(id),
  fetchPreferences(id)
]);

一旦 fetchOrders 接口挂了,整个函数直接抛异常,前端页面白屏。

根本原因:AI 训练数据中大量的代码示例只展示"正常流程",缺少异常处理的最佳实践。Promise.all 的"快失败"特性(任何一个 Promise reject 就立即 reject 整个集合)在实战中需要特殊处理。

预防措施

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 正确做法:用 Promise.allSettled
const results = await Promise.allSettled([
  fetchUser(id),
  fetchOrders(id),
  fetchPreferences(id)
]);

// 过滤出成功的请求
const successfulResults = results
  .filter(r => r.status === 'fulfilled')
  .map(r => r.value);

// 记录失败的请求(可选)
const failedRequests = results.filter(r => r.status === 'rejected');
if (failedRequests.length > 0) {
  console.error('部分请求失败:', failedRequests);
}

避坑关键点

  • 并发请求优先用 Promise.allSettled 替代 Promise.all
  • 必须显式处理 rejected 状态,记录或降级
  • 前端给用户明确提示"部分数据加载失败"

场景2:并发请求压垮后端服务

问题:AI 生成的批量操作代码没有限流,1000 个并发请求直接把服务打爆。

翻车现场

1
2
3
// AI 生成的代码
const users = await getAllUsers();
await Promise.all(users.map(user => sendEmail(user.email)));

测试环境 10 个用户没问题,生产环境 1000 个用户时后端直接 502。

根本原因:AI 的"暴力美学"倾向——遇到批量操作就默认并发。但真实的后端服务有并发上限(如 Nginx 的 worker_connections、数据库连接池),超限直接拒绝服务。

预防措施

1
2
3
4
5
6
7
8
9
// 使用 p-limit 限制并发数
import pLimit from 'p-limit';

const limit = pLimit(5); // 最多5个并发
const users = await getAllUsers();

await Promise.all(users.map(user =>
  limit(() => sendEmail(user.email))
));

避坑关键点

  • 批量操作必须加并发限流(推荐 3-10 个并发)
  • 数据库批量插入优先用 INSERT ... VALUES (...), (...), ... 语法
  • 外部 API 调用要考虑对方的速率限制

场景3:EventEmitter 内存泄漏

问题:AI 生成的监听器代码没有清理逻辑,长时间运行的 Node.js 服务内存持续增长,最终 OOM。

翻车现场

1
2
3
4
5
6
7
8
// AI 生成的代码
app.get('/api/users/:id', (req, res) => {
  const user = users.find(u => u.id === req.params.id);
  
  user.on('update', (data) => {
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  });
});

每次请求都注册监听器,但请求结束后监听器还在,内存不断泄漏。

根本原因:AI 的"单次思维"——只考虑"如何监听",不考虑"如何清理"。真实的服务器会处理数万次请求,每个监听器都是内存占用。

预防措施

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
app.get('/api/users/:id', (req, res) => {
  const user = users.find(u => u.id === req.params.id);
  
  const updateHandler = (data) => {
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  };
  
  user.on('update', updateHandler);
  
  // 关键:响应结束时清理监听器
  res.on('close', () => {
    user.off('update', updateHandler);
  });
});

避坑关键点

  • 所有事件监听器必须有对应的清理逻辑
  • 使用 AbortController 管理请求取消
  • 定期用 node --inspect 检查内存占用

场景4:SQL 注入漏洞

问题:AI 生成的数据库查询代码直接拼接字符串,导致 SQL 注入漏洞。

翻车现场

1
2
3
// AI 生成的代码
const query = `SELECT * FROM users WHERE name = '${name}'`;
const result = await db.query(query);

用户输入 name = "admin' OR '1'='1" 就能绕过登录验证。

根本原因:AI 的"简单优先"策略——拼接字符串比写参数化查询简单 5 倍,所以优先输出简单方案。但安全不是"简单优先"能解决的问题。

预防措施

1
2
3
4
5
6
7
8
// 使用参数化查询
const result = await db.query(
  'SELECT * FROM users WHERE name = ?',
  [name]
);

// ORM 框架自动防护
const user = await User.findOne({ where: { name } });

避坑关键点

  • 所有数据库查询必须用参数化或 ORM
  • CI/CD 流程中集成 sqlmap 自动扫描
  • 使用 TypeScript + Zod 做输入类型校验

场景5:XSS 跨站脚本攻击

问题:AI 生成的渲染代码直接输出用户输入,导致 XSS 攻击。

翻车现场

1
2
<!-- AI 生成的代码 -->
<div>{{ user.bio }}</div>

用户输入 <script>document.cookie='hacked'</script> 就能执行任意 JS。

根本原因:AI 的"功能优先"思维——先实现"能渲染",再考虑安全。但真实世界的用户输入充满恶意代码。

预防措施

1
2
3
4
5
6
7
8
<!-- React/Next.js 自动转义 -->
<div>{user.bio}</div>

<!-- 手动转义 -->
<div dangerouslySetInnerHTML={{ __html: escapeHtml(user.bio) }} />

<!-- 使用 DOMPurify 清洗 -->
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(user.bio) }} />

避坑关键点

  • 永远不要用 dangerouslySetInnerHTML,除非经过 DOMPurify 清洗
  • CSP(Content Security Policy)设置白名单域名
  • 使用 DOMPurify 的 SANITIZE_NAMED_PROPS 选项

场景6:接口超时没有重试机制

问题:AI 生成的 HTTP 请求代码没有超时和重试,偶发的网络抖动导致功能不可用。

翻车现场

1
2
// AI 生成的代码
const response = await fetch('https://api.example.com/data');

网络抖动一次,整个请求失败,用户看到"网络错误"提示。

根本原因:AI 的"理想化假设"——假设网络永远稳定、接口永远可用。但真实世界的网络充满了不确定性。

预防措施

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 使用 axios-retry
import axios from 'axios';
import axiosRetry from 'axios-retry';

const api = axios.create();
axiosRetry(api, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) => {
    return axiosRetry.isNetworkOrIdempotentRequestError(error);
  }
});

const response = await api.get('https://api.example.com/data', {
  timeout: 5000
});

避坑关键点

  • 所有外部请求必须设置超时(推荐 3-5 秒)
  • 幂等操作要重试(GET、PUT、DELETE),非幂等操作不要重试
  • 重试间隔用指数退避(1s → 2s → 4s)

场景7:没有边界条件检查

问题:AI 生成的代码假设"数据永远正常",遇到空数组、空对象直接崩溃。

翻车现场

1
2
3
// AI 生成的代码
const firstUser = users[0];
console.log(firstUser.name);

users 是空数组时,firstUserundefined,访问 .name 直接抛异常。

根本原因:AI 的"流程思维"——只考虑"正常流程",不考虑异常分支。但真实世界的数据总是充满了边界情况。

预防措施

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 使用可选链操作符
const firstName = users[0]?.name;

// 显式检查边界
if (users.length === 0) {
  return { error: 'No users found' };
}

// 使用 Zod 做运行时类型检查
const userSchema = z.object({
  name: z.string(),
  age: z.number()
});

避坑关键点

  • 所有数组/对象访问都要用可选链 ?.
  • TypeScript 的类型是编译时检查,运行时仍需 Zod 验证
  • 单元测试覆盖空数组、空对象、null、undefined 场景

场景8:文件上传没有大小限制

问题:AI 生成的文件上传代码没有大小限制,用户上传 1GB 文件直接打爆服务器。

翻车现场

1
2
3
4
// AI 生成的代码
app.post('/upload', upload.single('file'), (req, res) => {
  res.send('File uploaded');
});

用户上传 1GB 视频,磁盘瞬间被占满,服务崩溃。

根本原因:AI 的"功能优先"——先实现"能上传",不考虑存储限制。但服务器的磁盘是有限的。

预防措施

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Multer 配置文件大小限制
import multer from 'multer';

const upload = multer({
  limits: {
    fileSize: 10 * 1024 * 1024 // 10MB
  },
  fileFilter: (req, file, cb) => {
    const allowedTypes = ['image/jpeg', 'image/png'];
    if (!allowedTypes.includes(file.mimetype)) {
      return cb(new Error('Invalid file type'));
    }
    cb(null, true);
  }
});

避坑关键点

  • 文件大小限制在 10MB 以内(超过 10MB 建议用对象存储)
  • 文件类型白名单(只允许必要格式)
  • 上传前前端做预校验(大小、类型)

场景9:依赖项版本冲突

问题:AI 生成的代码安装了最新版本的依赖,但 API 不兼容导致项目无法运行。

翻车现场

1
2
# AI 生成的安装命令
npm install react@latest

安装了 React 19(最新版),但项目中的 UI 组件库只支持 React 18,直接报错。

根本原因:AI 的"最新即最好"假设——认为最新版本最稳定、功能最全。但真实世界的依赖升级往往伴随着 Breaking Changes。

预防措施

1
2
3
4
5
6
7
8
# 锁定主版本号
npm install react@^18.0.0

# 使用 package-lock.json 锁定精确版本
npm ci  # 严格按 package-lock.json 安装

# 使用 npm-check-updates 检查升级风险
npx ncu --interactive

避坑关键点

  • 依赖版本锁定主版本号(^18.0.0 而非 latest
  • CI/CD 流程中用 npm outdated 检查依赖过期
  • 重大升级前先在新分支测试

场景10:环境变量硬编码

问题:AI 生成的代码把数据库密码、API Key 写死在代码里,推送到 GitHub 导致泄露。

翻车现场

1
2
3
4
5
6
// AI 生成的代码
const db = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '123456' // 密码泄露!
});

代码推送到 GitHub,密码被扫描器发现,数据库被攻击。

根本原因:AI 的"快速原型"思维——先跑通再说,不考虑安全规范。但生产环境的配置泄露是灾难性的。

预防措施

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 使用环境变量
const db = mysql.createConnection({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD
});

// 使用 .env 文件(不提交到 Git)
// .env.example 提供模板
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=your_password

// .gitignore 中排除 .env
.env

避坑关键点

  • 所有敏感配置必须从环境变量读取
  • GitHub Secret 扫描自动检测密码泄露
  • 使用 dotenv-safe 确保 .env 文件完整性

避坑清单:可操作的行动指南

基于以上 10 个翻车场景,我总结了一份可操作的避坑清单,你可以直接集成到开发流程中:

开发前检查(AI 生成代码后)

  • 所有并发请求用 Promise.allSettled 替代 Promise.all
  • 批量操作必须加并发限流(p-limit
  • 所有事件监听器都有清理逻辑(AbortController
  • 数据库查询用参数化或 ORM
  • 用户输入渲染用 DOMPurify 清洗

代码审查 Checklist

  • 有没有硬编码的密码、API Key?
  • 外部请求有没有超时和重试机制?
  • 数组/对象访问用了可选链 ?.
  • 文件上传有没有大小限制?
  • 依赖版本是否锁定主版本号?

CI/CD 流程集成

  • sqlmap 扫描 SQL 注入漏洞
  • xsser 扫描 XSS 漏洞
  • npm audit 检查依赖安全问题
  • GitHub Secret 扫描自动检测泄露

生产环境监控

  • APM 工具监控接口响应时间
  • 内存泄漏告警(node --inspect
  • 错误日志集中收集(Sentry)

正确的 Vibe Coding 工作流

Vibe Coding 不是"完全不看代码",而是"接受代码无需逐行理解的事实,聚焦于结果是否符合预期"。以下是正确的 Vibe Coding 工作流:

  1. 需求描述:用自然语言清晰描述业务意图(“我要一个用户列表页,支持搜索、分页”)
  2. AI 生成:让 AI 生成初步代码(Cursor Composer、Claude Code)
  3. 快速验证:在开发环境运行,验证功能是否符合预期
  4. 安全加固:用上面的 Checklist 检查并修复常见漏洞
  5. 自动化测试:写单元测试覆盖核心逻辑和边界条件
  6. 生产部署:集成监控和告警,上线后观察指标

核心原则

  • Vibe Coding 适合"快速原型、内部工具、非核心业务"
  • 核心生产系统需要"Vibe 生成 + 人工审查 + 测试覆盖 + 安全扫描"
  • 开发者的职责从"亲手写代码"转向"定义目标 + 质量把关"

总结

Vibe Coding 极大提升了开发效率,但这不意味着可以放弃质量底线。上述 10 个翻车场景的共同特征是:AI 的"快速原型"思维与生产环境的"质量要求"不匹配

建立正确的 Vibe Coding 工作流,关键在于:

  1. 接受代码无需逐行理解,但必须用 Checklist 把控质量
  2. 集成自动化工具(安全扫描、测试框架)减少人工审查成本
  3. 区分"可 Vibe 场景"(原型、内部工具)和"需审查场景"(核心业务)

记住:Vibe Coding 是效率工具,不是免死金牌。守住质量底线,才能真正享受 AI 编程的红利。


关注 varkm,一起学习,一起成长。