5821 字
29 分钟
AI 出码工程手记:上下文、调试、并行与 Skill 的实践清单

在真实业务仓库里用 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 上下文、依赖三个外部服务、还要连库的老工程里,「让它补个测试」本身就是个大坑。按成本从低到高挑一个能跑通的就行:

  1. 只跑编译mvn -pl xxx-service -am compile 几乎零成本,已经能拦掉一半低级错误。任何情况下先把这条给它。
  2. 本地起服务 + curl。服务能起来的话这是性价比最高的——连 dev 库、打一次真实接口,配置、Bean 注入、SQL 是否跑得通一次全验。把 curl 命令连同参数写进 CLAUDE.md,它每次都能直接用。
  3. 切片测试,不起全 context@SpringBootTest 起不来时,用 @MybatisTest / @WebMvcTest 只加载需要的那层;外部依赖 @MockBean 掉。
  4. 实在不行,一个 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 代码不是省时间,是把时间挪到了三个月后的线上排查。

判断口诀:它擅长「你知道要什么、只是懒得打字」,不擅长「你还不知道要什么」。

一页纸#

  1. 写好 CLAUDE.md。 你觉得「新同事上手困难」的地方,就是 AI 出码质量差的地方——问题通常在工程本身,不在模型。
  2. 给锚点文件,不给整个仓库。 三个准确的参照物胜过两百个文件,但正例本身别带坏味道。
  3. 需求写成可验收的。 落点、复用点、边界、验收命令,缺一个就多一轮返工。
  4. 查 bug 先定位再动手。 给全堆栈和复现路径,明确说「先别改代码」。
  5. 让它能自己跑验证。 编译 / 测试闭环是质量的分水岭,不是锦上添花。
  6. 省 token 的本质是减少返工,不是压缩字数;开新会话是为了上下文干净,不是为了省钱。
  7. 并行按模块切,不按功能切。 worktree 隔离不了 pom.xml 和 flyway 版本号。
  8. 敏感文件不进上下文,生产权限不放行。
  9. Review 先分诊再人扫。 能自动化的交给 lint / SQL 审核,人眼只留事务、并发、权限、业务语义。
  10. 别信总结,看 diff。 subagent 的总结也一样。
AI 出码工程手记:上下文、调试、并行与 Skill 的实践清单
https://gilgameshzzz.github.io/posts/ai-dev-playbook/
作者
Amadeus
发布于
2026-09-15
许可协议
CC BY-NC-SA 4.0