🔥 AI 写代码总翻车?这套实战方法论救了我
摘要 :让 AI 写代码,要么"幻觉代码"一跑就炸,要么"屎山代码"能跑但改不动。本文是我学习 vibe-coding-cn 开源教程后的个人实践总结 + 踩坑复盘 ,包含 3 个真实项目的对比实验。不是教你写代码,是教你怎么指挥 AI 写出靠谱的代码。
📌 透明声明 :本文方法论框架参考了 vibe-coding-cn 开源教程,但所有实战案例、踩坑记录、对比实验均为个人独立实践。对原教程的部分观点,我也提出了不同看法。
📌 前言:我被 AI 坑了三次
第一次 :让 Claude 写一个 React 待办清单。我说 帮我写一个 react 待办清单页面,它噼里啪啦生成了 500 行单文件代码,title 和 text 字段混着用,能跑,但改不动。
第二次 :让 AI 写一个 Node.js 爬虫。它凭空捏造了一个 axios.getHTML() 方法------根本不存在的 API。我花了 20 分钟 debug 才发现是幻觉。
第三次:做一个电商后台,AI 自己设计了一套权限系统。上线后发现跟公司现有的 SSO 完全对不上,推倒重来。
三次翻车,三个不同的坑:屎山、幻觉、重复造轮子。
后来我系统学了一套方法论,重新用正确姿势做了一遍,效果天差地别。这篇文章就是我的踩坑复盘 + 实战总结。
🎯 本文适合谁
- ✅ 想用 AI 辅助编程但总踩坑的开发者
- ✅ 听过 Vibe Coding 但不知道怎么落地的同学
- ✅ 用 Cursor / Claude Code / Codex CLI 但效率不高的老手
- ✅ 想建立一套可复用的 AI 编程工作流的团队
🧠 第一章:搞清底层逻辑再动手
1.1 什么是 Vibe Coding
Vibe Coding = 用自然语言驱动,让 AI 生成大部分代码的开发方式。
由计算机科学家 Andrej Karpathy 首次提出。核心主张:先沉浸式做出能跑的东西。
但问题是 ------"能跑"和"能维护"是两码事。Karpathy 自己也说了,这是适合原型和一次性项目的玩法。如果你想做长期维护的项目,需要一套工程方法论来约束 AI。
1.2 我踩坑后总结的三条铁律
在学任何具体方法之前,先记住这三条,否则你永远在重复踩坑:
| 铁律 | 为什么 |
|---|---|
| 🛡️ AI 不能自证正确 | 永远不要让 AI 自己确认自己的输出。凡是能被测试、类型、schema 校验的规则,都应变成机器门禁 |
| 🧩 能复用时不重造 | 高阶形态不是从零生成代码,而是把已有成熟能力编排成业务系统 |
| 📋 先规划再编码 | 不上来就写代码,先强制 AI 读懂你的技术栈和项目规划 |
1.3 人机分工:谁干什么
很多人的误区是"让 AI 干所有事"。正确的分工是:
text
你负责:描述意图 → 设定约束 → 验证结果 → 做决策
AI 负责:理解上下文 → 生成计划 → 修改文件 → 整理证据
机器门禁:拦截幻觉 → 拦截糊弄 → 拦截越界 → 强制执行规范
💡 我的实测体会:把 AI 当成一个"能力很强但需要明确指令的新员工"。你不会让新来的员工直接干活,得让它先熟悉业务流程。AI 也一样------先让它读懂你的项目,再让它动手。
📐 第二章:先规划,再编码------我的 TodoList 实战
2.1 需求
用 React + TailwindCSS 实现一个任务清单页面,支持:
- ✅ 新增待办
- ✅ 删除待办
- ✅ 切换完成状态
- ✅ 拖拽排序
2.2 ❌ 错误示范(我的第一次翻车)
直接说:
text
帮我写一个 React 待办清单页面,支持新增、删除任务。
后果:
- AI 把所有逻辑堆在一个 500 行的
App.jsx里 - 字段名
title/text/name混着用 - 拖拽功能凭空手写一套坐标监听 + 排序算法
- 想改功能?无从下手
2.3 ✅ 正确示范(先规划,再编码)
第一步:强制规划,禁止输出代码
text
遵守胶水编程思维:优先使用成熟方案,避免凭空造逻辑。
第一个阶段:只做规划,禁止输出任何代码。
1. 确定技术栈:
React 19 + TailwindCSS + useState
2. 梳理功能边界:
- 新增待办、删除待办、切换完成状态
- 不做本地持久化、筛选功能
- 拖拽排序使用成熟库
3. 拆分模块(乐高组件):
- TaskInput:输入框组件
- TaskItem:单条待办组件
- TaskList:列表容器组件
4. 定义数据流:
useState 存储 tasks 数组
数据结构:{ id, text, completed }
5. 输出完整规划,等待确认后再分段实现代码。
💡 背后逻辑:
- 先划定边界 → 防止 AI 擅自加功能(我的第一次翻车就是没做这步)
- 强制模块拆分 → 好读好维护
- 预定义数据结构 → 从根源减少字段幻觉(
text不能让 AI 自己决定叫title还是name)- 规划文档伴随整个项目周期
2.4 第二步:胶水编程------拖拽排序
现在要加拖拽排序功能。
❌ 错误指令:
text
帮我写 React 待办清单的拖拽排序功能
AI 很可能凭空手写一套拖拽逻辑,case 覆盖不全,bug 满天飞。
✅ 正确指令(遵守胶水原则):
text
当前需求:给待办列表增加拖拽排序。
遵守胶水编程原则:绝不从零自研底层逻辑,
优先选择社区长期验证的成熟开源组件。
1. 先调研:React 生态成熟的拖拽库
2. 不要自己手写拖拽底层代码,只做粘合工作
3. 输出顺序:安装依赖命令 → 衔接现有组件 → 只写适配代码
AI 的调研结论:
react-beautiful-dnd已被 Atlassian 弃用,且不兼容 React 19。 选用社区维护的 fork@hello-pangea/dnd,API 完全一致,支持 React 19。
这就是胶水编程的精髓:不是自己造拖拽引擎,而是把成熟的拖拽库"粘"到现有组件上。
2.5 完整代码(可直接运行)
目录结构:
text
todolist/
├── src/
│ ├── App.jsx # 主容器,状态管理
│ ├── components/
│ │ ├── TaskInput.jsx # 输入框组件
│ │ ├── TaskItem.jsx # 单条待办(含拖拽)
│ │ └── TaskList.jsx # 列表容器(含 DragDropContext)
│ ├── index.css # TailwindCSS 入口
│ └── main.jsx # 应用入口
├── package.json
└── vite.config.js
App.jsx ------ 状态管理中枢:
jsx
import { useState } from 'react'
import TaskInput from './components/TaskInput'
import TaskList from './components/TaskList'
function App() {
const [tasks, setTasks] = useState([])
// 新增:预定义数据结构 { id, text, completed }
const handleAdd = (text) => {
setTasks((prev) => [
...prev,
{ id: Date.now(), text, completed: false },
])
}
const handleToggle = (id) => {
setTasks((prev) =>
prev.map((task) =>
task.id === id ? { ...task, completed: !task.completed } : task
)
)
}
const handleDelete = (id) => {
setTasks((prev) => prev.filter((task) => task.id !== id))
}
// 拖拽结束:数组重排(只做胶水,不做底层拖拽逻辑)
const handleDragEnd = (result) => {
if (!result.destination) return
const startIndex = result.source.index
const endIndex = result.destination.index
if (startIndex === endIndex) return
setTasks((prev) => {
const next = Array.from(prev)
const [moved] = next.splice(startIndex, 1)
next.splice(endIndex, 0, moved)
return next
})
}
const completedCount = tasks.filter((t) => t.completed).length
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 py-12 px-4">
<div className="max-w-xl mx-auto">
<div className="text-center mb-8">
<h1 className="text-4xl font-bold text-gray-800 mb-2">📝 待办清单</h1>
<p className="text-gray-500">共 {tasks.length} 项 · 已完成 {completedCount} 项</p>
</div>
<div className="bg-white rounded-2xl shadow-xl p-6">
<TaskInput onAdd={handleAdd} />
<TaskList
tasks={tasks}
onToggle={handleToggle}
onDelete={handleDelete}
onDragEnd={handleDragEnd}
/>
</div>
</div>
</div>
)
}
export default App
TaskInput.jsx ------ 输入框组件:
jsx
import { useState } from 'react'
function TaskInput({ onAdd }) {
const [text, setText] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
const trimmed = text.trim()
if (!trimmed) return
onAdd(trimmed)
setText('')
}
return (
<form onSubmit={handleSubmit} className="flex gap-2 mb-6">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="添加新待办..."
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<button
type="submit"
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors font-medium"
>
添加
</button>
</form>
)
}
export default TaskInput
TaskItem.jsx ------ 胶水粘合拖拽库:
jsx
import { Draggable } from '@hello-pangea/dnd'
function TaskItem({ task, index, onToggle, onDelete }) {
return (
<Draggable draggableId={String(task.id)} index={index}>
{(provided) => (
<li
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className="flex items-center gap-3 px-4 py-3 bg-white rounded-lg shadow-sm border border-gray-100 group cursor-grab active:cursor-grabbing hover:shadow-md transition-shadow"
>
<input
type="checkbox"
checked={task.completed}
onChange={() => onToggle(task.id)}
className="w-5 h-5 text-blue-500 rounded focus:ring-2 focus:ring-blue-300 cursor-pointer"
/>
<span className={`flex-1 text-gray-700 ${task.completed ? 'line-through text-gray-400' : ''}`}>
{task.text}
</span>
<button
onClick={() => onDelete(task.id)}
className="text-red-400 hover:text-red-600 opacity-0 group-hover:opacity-100 transition-opacity text-sm px-2 py-1 rounded hover:bg-red-50"
>
删除
</button>
</li>
)}
</Draggable>
)
}
export default TaskItem
TaskList.jsx ------ 列表容器:
jsx
import { DragDropContext, Droppable } from '@hello-pangea/dnd'
import TaskItem from './TaskItem'
function TaskList({ tasks, onToggle, onDelete, onDragEnd }) {
if (tasks.length === 0) {
return (
<div className="text-center py-12 text-gray-400">
<p className="text-lg">暂无待办事项</p>
<p className="text-sm mt-1">添加一个开始你的任务吧!</p>
</div>
)
}
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="task-list">
{(provided) => (
<ul ref={provided.innerRef} {...provided.droppableProps} className="space-y-2">
{tasks.map((task, index) => (
<TaskItem
key={task.id}
task={task}
index={index}
onToggle={onToggle}
onDelete={onDelete}
/>
))}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
)
}
export default TaskList
🎯 注意看 :我们没有写任何拖拽底层逻辑,只是把
@hello-pangea/dnd的<Draggable>组件"粘"到了<TaskItem>上。每个组件只做一件事,总共 4 个文件,每个都不超过 60 行。这就是胶水代码------短、薄、清晰、可删除。
🧬 第三章:胶水编程------从"实现者"到"整合者"
3.1 我的第二次翻车:凭空造 API
让 AI 写爬虫时,它生成了 axios.getHTML() 这种根本不存在的方法。为什么?因为我让它"从零实现",它就真的从零编了一个。
后来我学到一个原则:能抄不写,能连不造。
text
传统编程:人写代码
Vibe Coding:AI 写代码,人审代码
胶水编程: AI 连接成熟模块,人审连接
3.2 胶水原则(刻进 DNA)
| 原则 | 解释 |
|---|---|
| 成熟方案优于自研实现 | 别人踩过的坑,你不用再踩一遍 |
| 官方能力优于私有轮子 | 官方维护 = 长期稳定 |
| 复用优于重写 | 重写 = 重新引入所有 bug |
| 编排优于重造 | 用 A 的输出喂给 B,比自己造 C 强 |
| 可替换优于强绑定 | 今天用的库,明天可能弃维护 |
| 偏离必须说明 | 想自研?先写清楚为什么不用现成的 |
3.3 决策顺序(实战 checklist)
每次拿到一个需求,按这个顺序走:
- 🔍 先搜索:有没有官方能力、平台能力、成熟开源库?
- 📊 再评估:维护状态、许可证、文档质量、生产案例
- 🔗 再连接:只写胶水代码,把成熟模块粘在一起
- ✅ 再验证:测试、类型、schema、CI 全覆盖
- 🔄 留后路:替换路径、回滚方案
3.4 我的第三次翻车:重复造轮子
做电商后台时,AI 自己设计了一套权限系统。上线后发现跟公司现有的 SSO 完全对不上。
正确做法:先问 AI "有没有成熟的认证方案",它会告诉你 Auth0、Firebase Auth、Keycloak 这些。你只需要写胶水代码,把认证结果接入你的业务用户体系。
💡 胶水编程的精髓:轮子别人造好,为我所用;你只做胶水;胶水不生产零件,只联通零件。
3.5 对胶水编程的批判性思考
胶水编程不是万能的。我在实践中发现了几个局限性:
-
过度依赖外部库的风险 :如果上游库突然弃维护(比如
react-beautiful-dnd),你得紧急迁移。所以胶水层要做好隔离,别让第三方 API 渗透到核心业务模型里。 -
"成熟方案"的判断标准因场景而异:一个库有 10K star 就算成熟吗?不一定。要看它是否被真实生产环境使用、是否有活跃维护、是否与你的技术栈兼容。
-
AI 有时候比你更懂"有什么轮子" :这是好事也是坏事------好在它能帮你发现你不知道的工具,坏在它可能推荐一个看起来活跃但实际有严重 bug 的库。所以评估环节不能跳过。
🛠️ 第四章:真实项目对比------TodoList vs 爬虫 vs 后台
我把三个项目的经验放在一起对比,让你直观看到方法论的威力:
| 维度 | ❌ 无规划(翻车) | ✅ 有规划(成功) |
|---|---|---|
| TodoList | 500 行单文件,字段混乱 | 4 个组件,每个 < 60 行 |
| 爬虫 | 凭空捏造 axios.getHTML() |
用 playwright + 胶水代码 |
| 电商后台 | 自研权限系统,与 SSO 不兼容 | 用 Keycloak + 适配层 |
关键差异 :不是 AI 能力的问题,是你给 AI 的指令质量的问题。
🐞 第五章:避坑指南------8 条入门铁律
踩过的坑,总结成铁律,建议打印贴墙上:
| # | 铁律 | 为什么 |
|---|---|---|
| 1 | 先定义问题,再让 AI 写代码 | 问题不清,代码必乱 |
| 2 | 先让 AI 给计划,再让 AI 执行 | 计划是约束 AI 的缰绳 |
| 3 | 每一步都要能验证 | "看起来对" ≠ 真的对 |
| 4 | 频繁提交 Git | 每次进展都是可回滚的检查点 |
| 5 | 让文档持续更新 | README、AGENTS、progress.md 不能断 |
| 6 | 不相信 AI 的口头保证 | 只相信测试输出、CI 状态、可审查 diff |
| 7 | 重要规范必须代码化 | 能写成 lint/test/schema 的,就别只写自然语言 |
| 8 | 不符合规范必须失败 | 靠人记住提醒 AI = 迟早忘 |
5.1 Debug 最小化
当 AI 生成的代码出错时,不要把整个项目丢给它。正确的做法:
text
预期行为:点击"添加"按钮后,新任务出现在列表末尾
实际行为:点击后列表没有变化
最小复现:只贴 TaskInput.jsx 和 App.jsx 的相关代码
只给预期、实际和最小复现,AI 的修复准确率会大幅提升。
5.2 隔离审查
重要产出必须隔离审查 ------这是我认为整套方法论中最重要的一条:
新开一个会话,明确告诉审查 AI: "上一轮结果不可信,不能沿用结论,必须重新阅读原始资料,用事实和测试判断是否成立。"
为什么? 因为 AI 有"自偏好"------它倾向于认为自己的输出是正确的。同一个上下文里让它自我审查,等于让考生自己改卷子。
AI 负责生成候选解,隔离上下文负责审查,事实与验证负责裁决。
🔮 第六章:进阶------让 AI 帮你优化提示词
这一章分享一个我实测有效的进阶技巧:用 AI 优化你给 AI 的指令。
6.1 实战案例:从"能用"到"好用"
我最初的规划 prompt 是这样的:
text
帮我做一个 React 待办清单,用 TailwindCSS。
然后我让另一个 AI(或同一个 AI 的新会话)帮我优化这个 prompt:
text
我有一个给 AI 的指令,用来生成 React 待办清单。
请帮我优化这个指令,让它:
1. 更具体,减少 AI 自由发挥的空间
2. 包含明确的验收标准
3. 强制模块化拆分
4. 预定义数据结构,减少字段幻觉
原始指令:帮我做一个 React 待办清单,用 TailwindCSS。
优化后的 prompt 就是前面第二章的那个强制规划版本------效果天差地别。
6.2 递归优化(元方法论)
这个思路可以继续递归:
text
1. 用 AI 生成初始 prompt(v1)
2. 用另一个 AI 优化这个 prompt,得到 v2
3. 用 v2 生成更好的代码
4. 把代码中的问题反馈回去,再次优化 prompt
5. 循环,直到效果稳定
💡 我的体会:这个方法在复杂项目中特别有效。一个经过 3 轮优化的 prompt,生成代码的一次通过率从 40% 提升到了 80%+。但要注意,不要过度优化------有时候一个简单直接的指令就够了。
6.3 局限性
- 递归优化需要额外的时间成本,小项目不划算
- 优化后的 prompt 可能过拟合于特定场景,换个项目又得重新来
- 本质上是在弥补当前模型的不足------当模型足够强时,这些技巧可能不再需要
📊 第七章:工具清单速查
⚠️ 以下工具推荐基于 2026 年 7 月的实测体验,AI 工具迭代很快,请以实际使用为准。
AI CLI 选择
| 工具 | 特点 | 推荐指数 |
|---|---|---|
| Claude Code | 终端原生,深度思考能力强 | ⭐⭐⭐⭐⭐ |
| Codex CLI | OpenAI 官方,代码生成质量高 | ⭐⭐⭐⭐⭐ |
| Cursor | IDE 集成,上手简单 | ⭐⭐⭐⭐ |
| Gemini CLI | 免费额度,适合脚本和文档 | ⭐⭐⭐ |
| Kimi / 通义千问 | 国产模型,中文友好 | ⭐⭐⭐ |
提升提示词效果的技巧
text
// 触发深度思考(Claude Code)
think < think hard < think harder < ultrathink
// 万能后缀
"慢慢想,不着急,重要的是严格按我说的做,执行完美。
如果我表达不够精确请提问。"
上下文管理
- 经常用
/clear或/compact清理上下文 - 用
/rewind回滚到之前状态 - 每完成一步就
/new新建聊天,避免上下文污染
💡 总结:从"让 AI 写代码"到"指挥 AI 交付产品"
| 阶段 | ❌ 新兵做法 | ✅ 正确做法 |
|---|---|---|
| 拿到需求 | 直接让 AI 写代码 | 先规划、定边界、拆模块 |
| 技术选型 | AI 说啥用啥 | 先调研成熟能力,用胶水粘合 |
| 写代码 | 一个文件 500 行 | 模块化,每块只做一件事 |
| 出了 bug | 把整个项目丢给 AI | 预期 + 实际 + 最小复现 |
| 代码审查 | 自己看一遍就完事 | 隔离会话,AI 审 AI |
| 交付 | 跑起来就行 | 测试 + CI + Git + 文档 |
记住一句话:
🎯 Vibe Coding 不是把代码外包给 AI,而是一套工程协作方式。人负责目标和验收,AI 负责执行和放大,机器门禁负责兜底。
🔗 参考资料
- vibe-coding-cn:Vibe Coding 从入门到精通教程
- Andrej Karpathy 关于 Vibe Coding 的原始推文
- Claude Code 官方文档
- 拼好码(胶水编程的超集)
💬 交流讨论
你在用 AI 写代码时踩过哪些坑?有什么独门技巧?欢迎在评论区分享!
如果这篇文章对你有帮助,点个赞👍 收藏⭐ 关注👆,后续会更新更多 Vibe Coding 实战玩法:
- 📦 如何用 Vibe Coding 快速搭建全栈项目
- 🤖 Claude Code 自定义 Skill 实战
- 🔄 用递归优化让 AI 自己改进自己的提示词
觉得有用?点个赞👍收藏⭐关注👆,下次更新更多 AI 编程玩法!