在真实业务仓库里用 Claude Code / Cursor / Codex 做开发,攒下来的一份实践清单:怎么让它写出能合进主干的代码、怎么查线上 bug、并行跑多个 agent 会撞什么、把重复流程封装成 skill 时会踩哪些坑。
(面试题速查另开了一篇:《AI 开发岗面试速查》。)
先给结论:
出码质量 ≈ 上下文质量 × 约束明确度 × 反馈闭环
三项是乘法关系——任何一项接近 0,换更强的模型也救不回来。绝大多数「AI 写的代码不能用」,根因在前两项。
一、上下文工程:让它「看见」你的工程
投入产出比最高的一件事,而且是一次性投入、长期复利。
写一份真正能用的 CLAUDE.md / AGENTS.md
不是写项目简介,是写新人 onboarding 文档。判断标准:一个刚入职的同事只看这份文档,能不能提交一个不被打回的 PR。
必须写的六块:
技术栈与版本 Spring Boot 2.7 / JDK 11 / MyBatis-Plus,不要用 record、不要用 var目录职责 xxx-api 只放 DTO;xxx-service 放业务;禁止 controller 直连 mapper命名与分层 Service 接口 + Impl;DTO/VO/DO 三层不互穿,转换走 Converter禁用写法 禁止 System.out;禁止裸 RuntimeException;禁止在循环里查库改动前必读 新增接口先看 XxxController;新增表先看 db/migration 的 flyway 命名规范验证命令 mvn -pl xxx-service -am compile一条经验:与其写「要符合项目风格」,不如写「参照 OrderServiceImpl 的分层和异常处理方式」。模型抄得比理解得好——给正例的效果远超给规则。
代价是这把刀双刃:你挑的正例如果本身有坏味道,它会连坏味道一起复制放大。挑锚点文件时按「我希望新同事照着写的那个」来挑,而不是「最像的那个」。
控制信噪比,而不是塞满窗口
很多人以为上下文越多越好,实际是反的。无关文件会稀释注意力,还会让它「学」到你不想要的旧写法。
典型翻车:
「这是我们整个仓库,帮我加个导出功能」
它会在 200 个文件里挑一个最像的模仿,而那个文件很可能是三年前没人维护的老模块。
改成:
「参照
@OrderExportController的写法,在@ReportController里加导出接口,Excel 工具用@ExcelUtil」
明确的 3 个锚点文件,比 200 个文件的召回准确率高一个量级。
陈旧上下文比没有上下文更危险
长会话里,模型记得的是文件几十轮之前的样子。你手改过、或者它自己改过之后再讨论别的,很容易基于旧版本继续编辑,产出看似合理实则冲突的 diff。
难的地方在于察觉——它不会报错,只会平静地基于旧版本继续。几个可靠的信号:
- 它引用的行号对不上,或者提到一个你已经删掉的方法名
- 它说「我看到这里还是
xxx」,而你刚改过 - 它给的 diff 上下文行和文件当前内容有细微出入
- 它「修复」了一个你上一轮已经修过的问题
看到任意一条,立刻让它重读文件,别争论。
习惯:关键改动前让它重新读一遍当前文件;重构完立刻开新会话。
二、需求怎么下:写成可验收的
模棱两可的需求,模型不会来问你,它会自己编一个答案然后写 300 行。
含糊的说法:
帮我加个数据导出功能
结果:新造一个 ExcelHelper、自己定义一套错误码、全量 load 到内存、权限裸奔。
可验收的说法:
在
ReportController加/export:
- 复用现有
ExcelUtil,别新建工具类- 分页拉取,单页 2000,禁止全量 load
- 权限走现有
@PreAuthorize('report:export')- 错误码用
BizErrorCode,不要新增枚举- 改完执行
mvn -pl report-service -am compile
每一条都对应一个能当场判对错的检查点。
四个必写要素
- 落点:改哪个文件 / 哪个类,不写它就会新建。
- 复用点:能用的现成工具、基类、常量。不写就一定会重复造轮子,这是最高频的返工原因。
- 边界:不许动什么、不许引什么依赖、不许改表结构。
- 验收:跑什么命令算通过。
大改造先出方案,别直接动手
超过 3 个文件的改动,先让它输出实施计划(Claude Code 的 plan mode,或直接说「先别写代码,先讲方案」)。你确认思路再放行。方案阶段推翻一次的成本是几秒,代码阶段是十分钟加一次心态崩溃。
粒度陷阱:一次让它改 20 个文件,你不会认真 review,它也知道你不会。把改动切到「一个可 review 的 commit」——通常 3~8 个文件。粒度失控是 AI 代码埋雷的头号温床。
例外是机械性批量改动:全仓重命名、依赖升级、批量替换废弃 API。这类改动每个文件的变更模式完全一样,几十个文件反而是 AI 最擅长也最安全的场景——你 review 的是「模式对不对」,不是逐文件读。区分标准:这 20 个文件的改动是同一件事,还是 20 件事。
三、调试既有 bug:和写新功能是两套方法
日常里「线上报错,帮我查」比「帮我写个新功能」高频得多,但方法论完全不同,也更容易翻车。
最大的坑:信息不足时它不会说「我不知道」,它会从第一个可疑点开始猜,然后「修」三个不相干的地方。 你收到一个五文件的 diff,其中零个碰到了真正的根因。
先给料,再提问
必须给的三样:
- 真实堆栈 / 错误日志(完整的,别截前三行)
- 复现路径:什么请求、什么参数、什么数据状态下触发
- 边界:什么情况下是正常的——「同样的接口 A 用户正常,B 用户报错」比任何描述都值钱
强制「先定位,再动手」
这是 plan mode 在 debug 场景的对应物,一句话就够:
先告诉我根因在哪个文件哪一行,为什么会走到那里。先别改代码。
它给出定位后你自己判断一眼——这一步筛掉了绝大多数南辕北辙。定位对了再说「可以改了」。
它爱盖症状,不爱修根因
最高频的三种「假修复」:
- 加
try-catch把异常吞了,接口不报错了,数据还是错的 - 加一堆
if (x != null),NPE 消失了,为什么是 null 没人知道 - 改调用方绕开问题,而不是修被调用方
对策:验收时问一句「如果不加这个判空,为什么它会是 null?」答不上来就是没找到根因。这和 Review 清单里的「异常吞掉」是同一个病。
四、多 agent 并行:能拆什么,不能拆什么
subagent 分工、git worktree 并行跑多个任务,是现在的常态形态,但不是所有活都能拆。
能丢给 subagent 的
共同特征是输入是「仓库」,输出是「一段结论」,中间过程你不需要看:
- 大范围搜索:「哪些地方还在用这个废弃方法」
- 跨模块调研:「订单状态机一共有几个流转入口」
- 独立维度的审查:一个查并发问题、一个查权限、一个查 SQL,各跑各的
不能丢的
需要你脑子里运行时上下文的改动。 subagent 拿不到你对这张表有多少行、这个接口 QPS 多高、这段逻辑三个月前为什么这么写的记忆——它只能看到代码。这类改动交出去,回来的是「语法正确、业务错误」。
subagent 的总结同样比代码乐观
这是第五章「别信总结」的放大版,而且更危险:主 agent 收到的只有总结,它没看过 diff,会把那段乐观描述当事实继续往下做,误差就此被固化进后续所有决策。
对策:subagent 的结论当线索用,不当结论用。要它带上可验证的定位(文件 + 行号),你或主 agent 自己去看一眼原文。
并行改同一个仓库,worktree 解决不了什么
worktree 给的是文件系统隔离,不是语义隔离。Spring Boot 仓库上并行跑两个任务,典型的撞车:
| 冲突点 | worktree 能解决吗 |
|---|---|
改同一个 .java 文件 | 能隔离,但合并时照样冲突 |
各自往 pom.xml 加依赖 | 不能,合并冲突几乎必然 |
| 各自新建 flyway 脚本 | 不能,而且最坑——两个 V1.14__xxx.sql,本地各自能跑,合进主干直接崩 |
| 各自往同一个枚举 / 常量类加值 | 不能 |
| 各自起服务占同一个端口 | 不能,要手动错开 |
实际做法:并行任务按模块切,不按功能切;flyway 版本号和端口在派活时就人工分配好,别指望它们自己协调。
五、反馈闭环:让它自己验证
这是「能用的 agent」和「高级补全」之间的分水岭。
有编译、测试、lint 的项目,出码质量会断崖式提升——因为它能自己跑、看报错、自己修,你收到的是已经跑通的结果,而不是一份待调试的草稿。
| 层次 | 手段 | 它能自己发现什么 |
|---|---|---|
| 编译 | mvn -pl x -am compile | 类型、引用、导包、方法签名——一半的低级错误 |
| 测试 | mvn test -Dtest=XxxTest | 逻辑错误、边界、回归 |
| 运行 | 起服务 + curl 打一次接口 | 配置、Bean 注入、SQL 实际是否跑得通 |
老项目没有测试也别放弃——但在一个起不来 Spring 上下文、依赖三个外部服务、还要连库的老工程里,「让它补个测试」本身就是个大坑。按成本从低到高挑一个能跑通的就行:
- 只跑编译。
mvn -pl xxx-service -am compile几乎零成本,已经能拦掉一半低级错误。任何情况下先把这条给它。 - 本地起服务 + curl。服务能起来的话这是性价比最高的——连 dev 库、打一次真实接口,配置、Bean 注入、SQL 是否跑得通一次全验。把 curl 命令连同参数写进 CLAUDE.md,它每次都能直接用。
- 切片测试,不起全 context。
@SpringBootTest起不来时,用@MybatisTest/@WebMvcTest只加载需要的那层;外部依赖@MockBean掉。 - 实在不行,一个 main 方法。不优雅,但能跑就比不能跑强。
关键不是测试写得多正规,是让它有一条能自己反复跑的路径。
别信总结,看 diff:它写的完成总结和 commit message,永远比代码本身乐观。「已完成并通过测试」经常意味着「我改完了,测试我没跑」。
六、Token 优化:钱和效果是同一件事
省 token 不只是省钱——上下文越干净,出码质量越高。两个目标同向。
先说最重要的:最贵的浪费不是 prompt 写长了,而是方向错了跑了二十轮。 一次 5 分钟的方案确认,省下的 token 比所有微观优化加起来都多。省 token 的第一性原理是减少返工,不是压缩字数。
第二重要的是在任务边界主动开新会话。注意这里的理由不是钱——有 prompt caching 在,续接会话的历史部分大多命中缓存,成本远低于「每轮重算全部历史」的直觉。真正的理由是上下文污染和注意力稀释:上一个任务的文件、被推翻的方案、改了一半的思路全堆在那里,它的判断会越来越糊。
至于自动 compact:长任务确实需要它兜底,但别把它当成不开新会话的理由。压缩后它对项目的记忆会变粗,常见表现是突然又开始重复造轮子。
剩下的是微观手段:
| 手段 | 做法 | 收益 |
|---|---|---|
| Prompt caching | 稳定内容(系统提示、规范文档)放最前面且逐字不变,变化的放后面。前缀一改,缓存全废。自己调 API 时才能直接控制顺序;在 Claude Code / Cursor 这类工具里,对应的做法是别频繁改 CLAUDE.md、别中途切换工具集 | 最高 |
| 检索代替通读 | 用 grep/glob 定位再读片段,而不是整文件塞进去 | 高 |
| 子代理隔离 | 大范围搜索丢给 subagent,主上下文只收结论(见第四章) | 高 |
| 编辑而非重写 | 用精确替换改文件,而不是整文件重新输出。输出单价显著高于输入,而且输入还能被缓存打折、输出不能;全量重写还容易顺手改坏无关行 | 中高 |
| 杂项 | 约束输出长度(「只给 diff」)、命令加 -q 别把十万行日志读进上下文、格式转换用小模型 | 低但常见 |
怎么知道自己是省了还是亏了
token 账单看不出问题——它只告诉你花了多少,不告诉你其中多少是白花的。
更值得看的一个数:返工轮数。同一个任务,从下达到你接受,中间来回了几轮?三轮以内说明需求写清楚了;超过五轮,问题几乎一定在需求或上下文,不在模型。按这个数复盘,比盯账单有用得多。
七、封装成 Skill:会踩的坑
把重复流程(日报、发版检查、代码审查)写成 skill / 自定义命令,收益很大,但坑也集中。
1. 触发描述写太泛(最高频) description 写成「处理文档相关任务」,结果它在不该触发时触发,或者该用的时候完全不认。 → description 里写具体触发词和场景,包括「什么时候不该用」。这段文本是唯一的路由依据。
2. 写得太长 把所有细节塞进一个 3000 行的 skill,反而稀释了关键指令,模型只执行开头和结尾。 → 主文件只留决策路径和必须遵守的规则,细节拆到 reference 文件按需加载。
3. 可移植性:环境与编码 两个表现,一个根因——skill 里藏了「只在你这台机器成立」的假设。
- 隐式环境依赖:能跑是因为你的 PATH 里有那个命令、venv 已激活、配置恰好在默认位置。换台机器全挂。
- 跨平台编码(Windows 尤其):中文输出直接
UnicodeEncodeError,写文件写成 GBK 后续读取乱码;&&在 PowerShell 5.1 里是语法错误。
→ 显式写清依赖和绝对路径,开头做一次前置检查、缺失就明确报错;统一指定 UTF-8;路径用 os.path.join;命令链别假设 bash 语法。
4. 默认值悄悄生效 skill 里留了示例默认值(某个用户名、某个环境、某个目录),调用时没覆盖,它就真的用了,还不报错——最后产出的是别人的数据。 → 必填参数缺失就中断,不要给「看起来合理」的兜底默认值。
5. 不幂等 重跑一次就追加一遍、发两条消息、建重复记录。AI 执行本来就会重试。 → 写操作先判断是否已存在;对外发送类动作加显式确认。
6. 权限边界模糊 skill 里放了能连生产、能改配置、能推送的能力,某天在一个无关任务里被顺手调用。 → 读写分离成两个 skill;危险动作强制人工确认;生产凭据绝不进 skill 文件。
7. 难调试、会漂移 出问题时你不知道是 skill 写得不对、参数传错、还是模型没按指令走;而且模型版本一升级,原来稳定的 skill 行为就变了。 → skill 里要求打印关键中间步骤;准备 2~3 个固定输入的回归用例。
回归用例不需要很正规,形式就是「固定输入 + 必须出现的关键字 + 不得出现的关键字」。比如日报 skill:
输入: --date 2026-09-15 --user zhousongyao必须有: 日期行、至少一条 commit、结尾的「明日计划」小节不得有: 示例默认用户名、"TODO"、空的小节标题换模型后跑一遍,两分钟,能挡住绝大多数行为漂移。
什么该做成 skill?判断标准:同一个流程你已经手动解释过三次以上,且步骤稳定。流程本身还在变的,先别封装——你会花更多时间维护 skill 而不是干活。
八、安全:AI 工具链自身的风险面
这块最容易被跳过,但踩一次就是事故。
敏感文件会被读进上下文
它读配置文件找 Bean 定义时,会顺带把 application-dev.yml 里的数据库密码、AK/SK 一起读进上下文,然后出现在会话记录里、出现在它复述配置的回答里。
对策:
- 用
.claudeignore/ 工具的忽略配置排掉*-dev.yml、*-local.yml、.env、密钥目录 - 本地配置根本不进仓库——这是治本的那一条
- 已经进过 git 的凭据,删文件没用,要先轮换再清历史
它很喜欢「顺手」硬编码密钥
写测试、写示例、写临时脚本时,它会把连接串、token 直接写进代码——因为这样「能跑」,而它的目标函数是能跑。
对策:在 CLAUDE.md 里写死一条「任何凭据一律从环境变量或配置中心读,禁止出现在源码和测试里」,并把它放进 Review 清单逐次扫。
权限边界要提前定,不要临场判断
自动批准的范围一旦放宽,就不会再收紧。建议的分界:
| 类别 | 处理 |
|---|---|
只读(grep、读文件、git diff、编译) | 放开自动执行 |
| 本地写(改文件、跑测试) | 放开,但靠 git 兜底 |
git push、改 CI 配置、发消息 | 每次确认 |
| 连生产、改生产配置、删数据 | 绝不放行,凭据根本不给 |
这和第七章第 6 坑是同一件事的两面:skill 是能力的封装,权限模式是能力的闸门。
间接注入
从第三方 MCP server、依赖的 README、网页抓来的内容里,可能带着「忽略之前的指令」这类文本。心智和防用户输入一样:模型读到的一切外部内容都是不可信输入,别让它直接驱动危险动作。
九、Review 清单:AI 最容易写得「看起来对」的地方
它的语法和结构几乎不会错,所以错误全部集中在语义层。
先分诊,再逐条扫
十条清单逐行人工扫 8 个文件的 diff,没人扛得住,扫两次你就不扫了。先把能自动化的交出去:
| 谁来查 | 查什么 |
|---|---|
| lint / ArchUnit | 分层违规(controller 直连 mapper)、禁用 API、命名规范、新建工具类 |
| SQL 审核 / explain | 全表扫描、缺索引、没有 limit |
| 静态扫描 | 空指针路径、资源未关闭、硬编码凭据 |
| 只能人眼 | 事务边界、并发与幂等、权限、业务语义 |
规则一次性写好就永久生效,而且对人写的代码同样有用。人眼只留最后那四类——它们都依赖你脑子里的运行时上下文,工具查不了。
清单
- 事务边界:
@Transactional加在哪层、有没有被内部调用绕过、有没有把远程调用包进事务里 - 循环内查库 / 调服务:它非常喜欢写 for 里面调
selectById,功能正确但上线即事故 - 全量加载:没有分页、没有 limit、导出直接 list 全表
- 并发与幂等:重复提交、状态机跳变、缺乐观锁。它默认单线程思维
- 权限校验:新接口漏掉注解,或只在前端拦截。数据权限尤其容易漏
- 异常吞掉:catch 之后只打日志继续往下走,错误被静默
- 空值与边界:空列表、null、除零、时间区间左右闭开
- 重复造轮子:项目里已有的工具类被无视,又写了一个几乎一样的
- 字段与命名幻觉:用了不存在的表字段或方法名——编译能过的场景(Map 取值、SQL 字符串)最危险
- 顺手改了无关代码:全量重写文件时改掉了不该动的行,diff 里一定逐行确认
这几类不是「模型不够强」,而是它缺少你脑子里的运行时上下文——数据量多大、并发多高、这张表线上有多少行。把这些写进 CLAUDE.md,一半问题会在源头消失。
十、什么时候不该用 AI
上面全在讲怎么用得更好,这节讲反向边界——有些活自己写更快,硬上是负收益。
- 一次性改三行。 你描述清楚落点和意图要一分钟,自己改要二十秒。别为了用而用。
- 你自己都没想清楚的设计。 它会把你的模糊具体化成一个看起来很完整的方案,然后你花更长时间辨认这方案到底对不对。想清楚再开口,或者只让它当白板陪你推演、明确不出码。
- 涉及复杂业务口径的核心计算。 「这个指标怎么算」的答案不在代码里,在产品和运营脑子里。它只能从现有代码猜,猜错了还写得特别自信。这类先把口径用中文写死,再让它翻译成代码。
- 需要和人反复确认语义的地方。 瓶颈是沟通,不是打字,AI 加速不了。
- 你不打算 review 的改动。 这条最硬:不 review 就别让它写。没被 review 的 AI 代码不是省时间,是把时间挪到了三个月后的线上排查。
判断口诀:它擅长「你知道要什么、只是懒得打字」,不擅长「你还不知道要什么」。
一页纸
- 写好 CLAUDE.md。 你觉得「新同事上手困难」的地方,就是 AI 出码质量差的地方——问题通常在工程本身,不在模型。
- 给锚点文件,不给整个仓库。 三个准确的参照物胜过两百个文件,但正例本身别带坏味道。
- 需求写成可验收的。 落点、复用点、边界、验收命令,缺一个就多一轮返工。
- 查 bug 先定位再动手。 给全堆栈和复现路径,明确说「先别改代码」。
- 让它能自己跑验证。 编译 / 测试闭环是质量的分水岭,不是锦上添花。
- 省 token 的本质是减少返工,不是压缩字数;开新会话是为了上下文干净,不是为了省钱。
- 并行按模块切,不按功能切。 worktree 隔离不了
pom.xml和 flyway 版本号。 - 敏感文件不进上下文,生产权限不放行。
- Review 先分诊再人扫。 能自动化的交给 lint / SQL 审核,人眼只留事务、并发、权限、业务语义。
- 别信总结,看 diff。 subagent 的总结也一样。






