1. 为什么“让 AI 写代码”这件事,远没有看起来那么省心
我做了十多年全栈,从前端切图到后端调优、从数据库设计到线上排障,基本都亲手趟过一遍。这两年团队里陆续引入了各种 Coding Agent,从最早的代码补全,到后来能直接读整个仓库、自己改文件、跑测试的智能体,效率提升是实打实的。但我也见过太多人,包括一些工作三五年的工程师,把 AI 当成一个“许愿池”——丢一句“帮我写个用户登录模块”,然后就等着复制粘贴。结果呢?代码能跑,但埋了一堆坑:命名风格和项目格格不入、错误处理全靠try-catch兜底、边界条件一个没考虑、测试用例形同虚设。等到线上出问题,回头一看,AI 生成的代码里藏着一个空指针,排查了两小时。
所以我想聊的不是“AI 能不能写代码”,而是全栈工程师该怎么和 AI 协作。这个标题里的“5 条协作纪律”,不是拍脑袋想出来的口号,是我和团队在真实项目里踩坑、复盘、再迭代之后沉淀下来的规则。它解决的核心问题是:如何让 AI 的输出从“看起来能用”变成“真正可维护、可交付”。适合谁看?如果你是全栈工程师、技术负责人,或者正在把 AI 工具引入日常开发流程,那这些经验应该能帮你少走不少弯路。关键词里提到的 CLAUDE.md、AGENTS.md、Coding Agent,本质上都是这套协作纪律的载体——它们不是魔法文件,而是你和 AI 之间的“契约”。
2. 先搞清楚:AI 在全栈开发里到底扮演什么角色
2.1 它不是“替代者”,而是“高速实习生”
很多人对 AI 编程的期待是“我说需求,它出成品”。这个期待本身就错了。我习惯把 Coding Agent 类比成一个手速极快、知识面极广、但完全没有项目上下文的实习生。你让它写一个 React 组件,它能在三秒内给你一个结构完整的版本,但它不知道你们团队用的是 CSS Modules 还是 Tailwind,不知道你们的 API 请求封装在request.ts里还是用了 React Query,更不知道你们对错误提示的文案有统一规范。
所以第一条纪律就是:先给上下文,再给任务。这也是为什么 CLAUDE.md、AGENTS.md 这类文件会流行起来。它们的作用不是“配置 AI”,而是把项目里那些“老员工默认知道、但新人必须被告知”的信息,显式地写下来。比如:
- 项目用了什么技术栈、什么版本
- 目录结构约定(组件放哪、工具函数放哪、类型定义放哪)
- 代码风格(命名、注释、错误处理模式)
- 常用命令(怎么启动、怎么测试、怎么构建)
- 禁止事项(比如不要引入新的依赖、不要改某个核心文件)
我试过在一个中型项目里,把上面这些信息整理成一份AGENTS.md放在仓库根目录。效果非常明显:同一个 Coding Agent,在没有这份文件时生成的代码,我需要改 40% 才能合并;有了之后,改动量降到 10% 左右。这不是 AI 变聪明了,而是它终于知道“这个项目是怎么运转的”。
2.2 全栈场景下,AI 的强项和短板分别在哪
全栈工程师的工作横跨前端、后端、数据库、部署,AI 在不同环节的表现差异很大。我自己的体感是这样的:
| 环节 | AI 表现 | 原因 |
|---|---|---|
| 写样板代码 | 极强 | CRUD、表单、类型定义这类模式化工作,AI 几乎不会出错 |
| 写业务逻辑 | 中等 | 需要理解需求细节和边界条件,容易漏掉异常分支 |
| 调试排错 | 较强 | 能快速定位常见错误,但对环境相关问题容易误判 |
| 架构设计 | 较弱 | 缺乏对团队规模、业务演进、运维成本的全局判断 |
| 数据库优化 | 中等 | 能给出索引建议,但不懂你的真实数据分布和查询模式 |
| 前端交互 | 较强 | 组件拆分、状态管理、样式实现都很熟练 |
这张表不是要否定 AI,而是提醒你:把 AI 用在它擅长的地方,在它不擅长的地方保持人工判断。比如让 AI 写一个数据表格组件,它很快;但让它决定这个表格该用虚拟滚动还是分页,就需要你根据数据量和交互需求来判断。
2.3 为什么“纪律”比“工具”更重要
工具每天都在变。今天用这个 Agent,明天可能换另一个。但协作纪律是稳定的。我总结的 5 条纪律,本质上是在回答五个问题:
- 你怎么让 AI 理解你的项目?
- 你怎么把任务拆成 AI 能接住的粒度?
- 你怎么验证 AI 的输出?
- 你怎么在 AI 出错时快速定位?
- 你怎么让 AI 的产出和团队规范保持一致?
这五个问题,换任何工具都绕不开。下面我逐条展开,每条都会配上我在实际项目里的操作细节和踩坑记录。
3. 纪律一:先写“项目说明书”,再让 AI 动手
3.1 CLAUDE.md 和 AGENTS.md 到底该写什么
很多人知道要写这类文件,但写出来的内容要么太泛(“请写高质量的代码”),要么太细(把整个 API 文档贴进去)。我的经验是:只写 AI 猜不到、但每次都需要的信息。具体来说,分四块:
第一块:项目概览。用三五句话说明这个项目是干什么的、面向谁、核心功能是什么。比如“这是一个面向中小企业的库存管理系统,前端用 Next.js,后端用 FastAPI,数据库是 PostgreSQL”。这段话的作用是给 AI 一个“世界观”,让它在生成代码时不会跑偏。
第二块:目录结构与约定。列出关键目录和它们的职责。比如:
src/ components/ # 通用组件,每个组件一个文件夹 features/ # 按业务模块划分的功能代码 lib/ # 工具函数和第三方封装 types/ # 全局类型定义 app/ # Next.js 路由页面再补一句“新组件必须放在 components 下,并按 PascalCase 命名”。这样 AI 就不会把组件随手丢到utils里。
第三块:代码风格与模式。这块最容易被忽略,但影响最大。我会写清楚:
- 错误处理统一用
AppError类,不要直接throw new Error - API 请求统一走
lib/api.ts里的request方法 - 样式用 Tailwind,不要写内联 style
- 所有异步函数必须处理 loading 和 error 状态
第四块:常用命令与禁止事项。比如:
pnpm dev # 启动开发服务器 pnpm test # 运行测试 pnpm lint # 检查代码风格禁止事项写三条就够:“不要引入新的 npm 依赖”“不要修改lib/api.ts的导出签名”“不要删除现有测试用例”。
3.2 一个真实项目的 AGENTS.md 示例
我在一个电商后台项目里用的AGENTS.md大概长这样(脱敏后):
# 项目说明 电商后台管理系统,前端 Next.js 14 + TypeScript,后端 NestJS,数据库 MySQL。 # 目录约定 - src/components:通用 UI 组件 - src/features:业务模块,每个模块包含 components、hooks、api - src/lib:工具函数,api.ts 是统一请求入口 - src/types:全局类型 # 代码规范 - 组件用函数式,props 必须定义 interface - 错误处理用 AppError,禁止裸 throw - 样式用 Tailwind,禁止内联 style - 所有列表渲染必须加 key # 常用命令 pnpm dev / pnpm test / pnpm lint # 禁止 - 不要新增依赖 - 不要改 lib/api.ts 的导出 - 不要删测试这份文件不到 30 行,但效果立竿见影。AI 生成的组件会自动放在features对应模块下,错误处理会引用AppError,样式全是 Tailwind 类名。我只需要检查业务逻辑对不对,不用再花时间改格式。
3.3 注意事项:别把说明书写成“许愿池”
我见过有人把CLAUDE.md写成“请写出优雅、高效、可维护的代码”。这种话对 AI 没有任何约束力,因为它不知道“优雅”在你的项目里具体指什么。说明书要写“可验证的规则”,而不是“美好的愿望”。比如“函数不超过 50 行”比“代码要简洁”有用得多,“所有 API 调用必须包在 try-catch 里”比“注意错误处理”有用得多。
另外,这份文件要随项目演进更新。我一般会在每个迭代结束时花五分钟检查一下:有没有新的约定?有没有废弃的规则?保持它和项目现状一致,AI 才不会拿着过时的信息干活。
4. 纪律二:把任务拆到 AI 能“一口吃掉”的粒度
4.1 为什么“帮我写个登录模块”是糟糕的指令
“帮我写个登录模块”这句话,对人来说都太模糊,对 AI 更是灾难。它不知道你要的是:
- 前端表单 + 后端接口 + 数据库表,还是只做前端?
- 用 JWT 还是 Session?
- 要不要验证码?要不要记住我?
- 密码加密用 bcrypt 还是 argon2?
- 错误提示是弹 toast 还是显示在表单下方?
结果就是 AI 按自己的理解生成一套东西,你一看,方向全错,只能重来。任务粒度太粗,是 AI 协作效率低下的头号原因。
4.2 我的拆解模板:一个功能拆成 4 到 6 个任务
我习惯把一个功能拆成“数据层 → 接口层 → 状态层 → 视图层 → 测试”这样的链条。以“用户登录”为例:
- 数据层:定义 User 类型和登录请求/响应的类型
- 接口层:写
login函数,调用后端 API,处理错误 - 状态层:写
useLoginhook,管理 loading、error、成功后的跳转 - 视图层:写登录表单组件,包含输入校验和提交按钮
- 测试:写
useLogin的单元测试和表单的交互测试
每个任务单独交给 AI,每次只关注一个文件或一个函数。这样做的好处是:AI 的上下文窗口不会被无关信息占满,输出质量更高;你也能逐个验证,出错时容易定位。
4.3 实操:一次只让 AI 改一个文件
我有个硬性习惯:每次让 AI 改代码,只允许它动一个文件。如果任务确实需要跨文件修改,我会拆成多轮,每轮只改一个。比如“把登录接口从 fetch 改成 axios”,我会先让 AI 改lib/api.ts,确认没问题后,再让它改调用这个接口的 hook。
这个习惯来自一次教训。早期我让 AI“把项目里所有 fetch 调用改成 axios”,它一口气改了 12 个文件,结果有三个文件的导入路径写错了,还有两个文件漏改了错误处理。我花了半小时逐个排查。从那以后,我就坚持“一次一文件”,虽然看起来慢,但总体返工率大幅下降。
提示:如果你用的 Coding Agent 支持“只读模式”或“建议模式”,在拆解任务阶段可以先用它来确认理解是否正确,再让它动手改代码。
5. 纪律三:AI 写的代码,你必须逐行读懂再合并
5.1 “能跑”不等于“能维护”
AI 生成的代码有一个特点:它通常能跑,但未必符合你的项目习惯。比如它可能会用any类型绕过 TypeScript 检查,可能会在组件里直接写fetch而不走统一封装,可能会把业务逻辑和 UI 混在一个文件里。这些代码在本地跑起来没问题,但合并到主分支后,就成了技术债。
我的原则很简单:AI 写的每一行代码,我都要能解释它为什么在那里。如果某一行我看不懂,或者觉得“这样写也行但没必要”,我就会让 AI 重写或者自己改。这不是不信任 AI,而是作为工程师的基本责任——代码是你签的字,出了问题是你背。
5.2 重点检查这五个地方
逐行读代码时,我会特别关注五个高风险区域:
第一,类型定义。AI 有时会偷懒用any或unknown,或者把类型定义得过于宽泛。我会检查所有新增的类型,确保它们精确描述了数据结构。
第二,错误处理。AI 倾向于只处理“成功路径”,对网络错误、超时、空数据这些情况容易忽略。我会检查每个异步调用是否有对应的错误分支。
第三,边界条件。比如数组为空、数字为 0、字符串为空串时,代码是否还能正常工作。AI 生成的循环和条件判断,经常漏掉这些情况。
第四,副作用。比如useEffect的依赖数组是否完整,事件监听是否在卸载时清理,定时器是否清除。这些在 AI 生成的 React 代码里很常见。
第五,命名一致性。AI 可能会用handleSubmit,也可能用onSubmit,还可能用submitForm。我会统一成项目里已有的命名习惯。
5.3 一个真实的排查案例
有一次,我让 AI 写一个“根据用户角色显示不同菜单”的组件。它生成的代码逻辑上没问题,但我读的时候发现,它在useEffect里根据角色设置菜单状态,依赖数组只写了role。问题是,菜单数据是从一个 context 里取的,如果 context 更新了但 role 没变,菜单就不会刷新。这是一个典型的“AI 漏掉隐式依赖”的问题。我补上了menuConfig依赖,并加了一条注释说明为什么需要它。
这个案例说明:AI 能写出“看起来对”的代码,但只有你能判断它在你的项目上下文里是否真的对。逐行读代码,就是在做这个判断。
6. 纪律四:用测试和类型检查给 AI 输出上“保险”
6.1 为什么测试是 AI 协作的必需品
AI 生成代码的速度很快,但它的“自信”和“正确”之间没有必然联系。它可能会生成一个函数,签名完全正确,逻辑看起来也合理,但实际运行时因为一个边界条件就崩了。测试是验证 AI 输出最可靠的手段,因为它不依赖你的主观判断,而是用可重复的方式检查行为。
我的做法是:每个 AI 生成的核心函数,至少配一个测试用例。如果是纯函数,就测输入输出;如果是 hook,就测状态变化;如果是组件,就测渲染和交互。测试不用多,但要覆盖“正常路径 + 一个边界情况 + 一个错误情况”。
6.2 类型检查:第一道自动防线
TypeScript 的类型检查是成本最低的验证手段。AI 生成的代码如果类型不对,tsc会直接报错,你根本不用运行就能发现问题。我会在让 AI 改完代码后,立刻跑一遍pnpm tsc --noEmit,看看有没有类型错误。
这里有个小技巧:如果 AI 生成的代码里出现了any,我会让它解释为什么需要any。大多数时候,它其实可以用更精确的类型,只是偷懒了。让它解释一遍,它往往会自己改过来。
6.3 实操:给 AI 生成代码配测试的流程
我通常这样操作:
- 让 AI 生成业务代码
- 我自己读一遍,确认逻辑没问题
- 让 AI 为这段代码生成测试用例
- 我检查测试用例是否覆盖了边界情况
- 运行测试,如果有失败,让 AI 分析原因并修复
- 测试通过后,再合并代码
这个流程看起来多了一步“让 AI 写测试”,但实际上节省了大量手动测试的时间。而且 AI 写测试的速度很快,你只需要检查它有没有漏掉关键场景。
注意:不要让 AI 同时写业务代码和测试代码,然后直接信任测试结果。因为 AI 可能会写出“刚好能通过自己写的测试”的代码,但测试本身覆盖不全。正确做法是:你先明确要测哪些场景,再让 AI 按你的要求写测试。
7. 纪律五:保持“人在回路”,别让 AI 替你决策
7.1 哪些决策必须由人来做
AI 可以帮你写代码,但不能帮你做技术决策。以下这些事情,我坚持自己判断:
- 技术选型:用哪个库、哪个框架、哪个数据库,涉及长期维护成本,AI 给的建议往往只看当下
- 架构设计:模块怎么划分、服务怎么拆分、数据怎么流转,需要结合团队和业务来判断
- 性能取舍:要不要加缓存、要不要做懒加载、要不要预计算,需要知道真实的数据量和访问模式
- 安全策略:认证方式、权限模型、数据加密,这些容不得 AI 试错
- 发布节奏:什么时候上线、要不要灰度、回滚方案是什么,这是工程判断,不是代码问题
我的经验是:AI 可以给你选项和理由,但最终拍板必须是你。你可以让它列出“用 Redis 和用内存缓存各自的优缺点”,但选哪个,得你根据项目情况来定。
7.2 建立“AI 建议 → 人工审核 → 执行”的闭环
我在团队里推行的流程是这样的:
- AI 提出方案或生成代码
- 工程师审核,判断是否合理
- 如果有疑问,让 AI 解释理由
- 工程师做最终决定
- 执行并记录决策原因
这个闭环的关键是第 3 步和第 5 步。让 AI 解释理由,能帮你发现它是否真的理解了问题;记录决策原因,能在未来复盘时知道当时为什么这么选。
7.3 我的个人体会:AI 越强,判断力越值钱
用了两年多 Coding Agent,我最大的感受是:AI 把“写代码”这件事的门槛降低了,但把“判断代码好坏”的门槛提高了。以前你可能需要花很多时间写样板代码,现在 AI 几秒就写完了,但你需要有能力判断它写得对不对、好不好、适不适合你的项目。这种判断力,来自你对项目的熟悉、对业务的理解、对技术原理的掌握。AI 越强,这种判断力就越值钱。
所以我的建议是:别把时间省下来去摸鱼,把时间花在理解代码、理解业务、理解系统上。AI 帮你省下的时间,应该用来提升你的判断力,而不是让你变得更依赖它。
8. 常见问题与排查技巧实录
8.1 AI 生成的代码跑不起来,怎么快速定位
这是最常见的问题。我的排查顺序是:
- 看错误信息:TypeScript 报错通常很明确,直接定位到文件和行号
- 检查导入路径:AI 经常把相对路径写错,尤其是跨目录引用
- 检查依赖:AI 可能用了项目里没装的库,或者用了版本不兼容的 API
- 检查类型:如果报错是类型不匹配,让 AI 解释它为什么这么定义类型
- 最小化复现:把出错的代码单独拿出来,去掉无关部分,看是否还能复现
我遇到最多的情况是导入路径错误和依赖缺失。这两个问题占了 AI 代码报错的七成以上。
8.2 AI 总是改错文件,怎么办
这说明你的任务描述不够明确。AI 不知道你要改哪个文件时,会自己猜,猜错很正常。解决办法是:在指令里明确写出文件路径。比如不要说“改一下登录逻辑”,而要说“修改src/features/auth/hooks/useLogin.ts里的login函数,让它支持记住我功能”。
另外,如果你的 Coding Agent 支持“只读模式”,可以先让它列出它打算改哪些文件,你确认后再让它动手。这个习惯能避免很多误改。
8.3 AI 生成的代码风格和项目不一致
这是AGENTS.md或CLAUDE.md没写清楚导致的。检查你的说明书里有没有明确写:
- 命名规范(camelCase 还是 snake_case)
- 文件组织方式(按功能分还是按类型分)
- 错误处理模式(用自定义错误类还是直接 throw)
- 样式方案(Tailwind、CSS Modules 还是 styled-components)
如果写了但还是不一致,可能是 AI 的上下文窗口里信息太多,它“忘了”。这时候可以在指令里再强调一遍,比如“注意:错误处理必须用 AppError,不要直接 throw”。
8.4 常见问题速查表
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| 代码跑不起来 | 导入路径错误、依赖缺失 | 检查 import 语句和 package.json |
| 类型报错 | AI 用了 any 或类型定义不精确 | 让 AI 解释类型定义,或自己修正 |
| 逻辑不符合预期 | 任务描述太模糊 | 拆细任务,明确输入输出和边界条件 |
| 风格不一致 | 说明书没写清楚或 AI 忘了 | 补充 AGENTS.md,指令里再强调 |
| 改错文件 | 没指定文件路径 | 指令里写明完整路径 |
| 测试不通过 | AI 写的测试覆盖不全 | 自己明确测试场景,让 AI 按场景写 |
| 性能问题 | AI 用了低效实现 | 检查循环、查询、渲染逻辑,必要时自己优化 |
8.5 一个容易被忽略的坑:AI 会“过度设计”
AI 有时候会为了“显得专业”而引入不必要的复杂度。比如你让它写一个简单的工具函数,它给你搞了一个类、一个工厂函数、一个配置对象。这种“过度设计”在 AI 生成的代码里很常见。我的应对方法是:在指令里加一句“保持简单,不要引入不必要的抽象”。如果它还是写复杂了,就让它简化,直到代码量和你预期的一致。
9. 最后分享几个我常用的指令模板
9.1 让 AI 理解项目的指令
请先阅读项目根目录的 AGENTS.md,了解项目结构、代码规范和常用命令。 然后告诉我你理解了什么,确认后再开始任务。9.2 让 AI 写代码的指令
任务:在 src/features/auth/hooks/useLogin.ts 中实现 login 函数。 要求: - 调用 lib/api.ts 的 request 方法 - 处理 loading、error、success 三种状态 - 错误用 AppError 包装 - 不要引入新依赖 - 写完后附上单元测试9.3 让 AI 检查代码的指令
请检查你刚才生成的代码,重点看: 1. 是否有 any 类型 2. 是否处理了所有错误分支 3. 是否覆盖了空数组、空字符串、0 这些边界情况 4. 命名是否和项目现有代码一致 列出你发现的问题并修复。这三个模板我在日常工作中反复用,效果很稳定。核心思路就是:先对齐上下文,再给明确任务,最后让 AI 自查。这套流程跑顺了,AI 协作的效率和质量都会有明显提升。
我在实际项目里的体会是,AI 编程工具确实能大幅提升效率,但它放大的是你的能力,而不是替代你的判断。你把项目上下文给得越清楚,任务拆得越细,验证做得越扎实,AI 的输出就越可靠。反过来,如果你指望一句话就让 AI 写出生产级代码,那踩坑是迟早的事。这套协作纪律,本质上是在帮你把 AI 的速度优势,转化成真正可交付的工程成果。