用一段时间 Claude Code / Codex CLI 之后,多数人会形成一套模糊直觉:有些活能用它,有些不能用。这篇把那套直觉拆成可判断的东西——边界在哪四个维度上、失效模式有哪几类、任务开始前怎么预判。
1. 能力边界的四个维度
| 维度 | 边界在哪 | 触发失效的典型场景 |
|---|---|---|
| 上下文能装多少 | 窗口有限,且有效注意力低于名义容量 | 让它改一个 3000 行的巨型类 |
| 训练数据时间与领域 | 截止日期之后的 API 变更不在知识内 | 用新版本框架的推荐写法改写老代码 |
| 能否执行与观察 | 能跑命令不等于能看懂结果 | 跑通了构建,但没看出告警里的关键信息 |
| 能否长期记忆 | 跨会话无记忆,靠文件承载 | 上周定下的命名约定,今天它又忘了 |
前两个是硬边界,提问技巧突破不了;后两个是软边界,能用工程手段补(约定写进 AGENTS.md、要求贴出命令输出)。
一个常见误解:以为"窗口够大就没问题"。实际上窗口越大,位置靠中的材料被利用的效率越低(典型量级,按所用模型核对)。把 5000 行代码塞进 1M 窗口,效果不如精心挑出的 300 行。
2. 六类失效模式
2.1 幻觉 API 与配置项
// 看着像标准库,实际不存在
List<Order> first = orders.stream().filterIf(true, ...).toListOrEmpty();
# 键名编造:看起来对,启动时报 Unknown property
spring:
datasource:
hikari:
maximum-pool-size: 20 # 真实键名是 max-pool-size
共同特征是出错前无法从语法上判断,唯一可靠检验是编译与启动。
2.2 把旧版本写法当现行
| 领域 | 旧写法 | 现行写法 |
|---|---|---|
| Spring Security | WebSecurityConfigurerAdapter |
SecurityFilterChain Bean |
| HTTP 客户端 | RestTemplate 裸用 |
RestClient / WebClient |
| Java 集合 | Collections.unmodifiableList 手动包装 |
List.copyOf / Stream.toList() |
| 前端构建 | webpack.config.js 手写 |
Vite / 框架内置 |
默认输出倾向语料里频次最高的写法,而语料里老代码永远比新代码多。纠正方式:明确要求"按 OrderService 的现有写法来"。
2.3 忽略项目特定约定
它知道"Java 里怎么判空",不知道"这个项目禁止返回 null 集合"。这类失效不产生编译错误,只产生评审返工。
2.4 过度重构
让它改一个方法,它顺手改了命名、抽了接口、注入了两个新依赖。diff 从 20 行变成 400 行。
# 生成代码的"顺手优化"
- 加了 OrderMapperFactory 抽象层
- 把 3 处重复逻辑抽成 AbstractOrderHandler
- 引入 MapStruct 做 DTO 转换
每处单看都"更好",合起来是这次任务不该承担的风险。
2.5 只处理主路径
正常输入走通,异常、空值、超时、并发全缺。这是生成代码里最稳定的缺陷类型(见 X10 的检查点二)。
2.6 大文件与长链路的局部最优
让它改一个跨 8 个文件的调用链,它往往把每个文件都改得"局部合理",但整体契约对不上:上层传了 null,中层没判断,底层抛异常。这不是它不努力,是它一次只能看到一个断面的取舍。
3. "能编译"不等于"能用"
编译是一个极弱的正确性证明。它只排除语法错误和类型不匹配,剩下的全都不管。
| 检查 | 能发现 | 发现不了 |
|---|---|---|
| 编译 | 语法、类型、符号不存在 | 逻辑、边界、配置键名、并发 |
| 单测 | 被断言的路径 | 未覆盖的分支、真实装配 |
| 启动 | 配置加载、Bean 装配 | 运行期行为、数据形态 |
| 真实请求 | 端到端主路径 | 并发、大数据量、下游故障 |
| 灰度观察 | 真实流量下的异常分布 | 低频边界 |
所以"能编译"连前三档都算不上,它只是最低前提。验收至少要跨到"真实请求"这一档。
4. 知识时效性
三个具体表现:
- 废弃 API:仍生成已标记
@Deprecated的调用; - 版本行为变更:同一配置键在旧版是默认值,新版改了默认;
- 新语法缺失:不熟悉 record、模式匹配,回退到冗长写法。
# 确认项目实际版本,再决定"新写法"是否可用
$ grep -A2 "<parent>" pom.xml | head -5
$ ./mvnw -v
处理方式是把版本作为上下文显式给出:
项目使用 Spring Boot 3.3(Java 17)。禁止使用 WebSecurityConfigurerAdapter
与 javax.* 包;允许使用 record、switch 模式匹配。
不给版本信息时,它会按"最通用的写法"输出,而最通用往往等于最旧。
5. 安全与合规相关的失效
这一节不是道德讨论,是失效模式分类:这些地方出错的方式和其他地方不一样,因为错误不一定表现为功能异常。
| 失效 | 具体形态 | 后果 |
|---|---|---|
| 凭据处理 | 把连接串、密钥直接写进源码或测试 | 随代码进仓库 |
| 日志脱敏 | 打印整个请求体、完整手机号/卡号 | 敏感数据进日志系统 |
| 供应链 | 为解决小问题引入一个新的第三方包 | 依赖来源未评估 |
| 输入校验 | 信任前端参数,直接拼进 SQL 或路径 | 注入 |
| 错误信息 | 把异常栈、内部路径返回给调用方 | 信息泄露 |
共同点是不会让程序报错或变慢,测试永远发现不了,防线只能设在约定与评审里:
## AGENTS.md 片段
- 禁止在源码、测试、配置模板中写入真实密钥;用占位符 `${DB_PASSWORD}`
- 日志禁止打印完整身份证号、手机号、卡号;需脱敏到前 3 后 4
- 新增依赖需在 PR 说明:为什么不使用已有能力
- 对外错误响应只返回 code + 通用文案,异常栈只进日志
6. 不确定性表达的价值
合格的输出会标注"这里我不确定",不合格的输出会把不确定的地方硬编成确定的代码。
| 表现 | 价值 | 处理 |
|---|---|---|
"假设 status 只有 PENDING/PAID/CANCELLED 三种取值" |
高 | 直接确认这条假设 |
"这里用了 @Transactional 默认传播行为,若需新事务请说明" |
高 | 明确需求 |
"根据常见实现推断该类存在 findById 方法" |
高 | 立刻核对真实签名 |
| 什么都没说,代码里写死了判断 | 低 | 逐处追问"你的依据是什么" |
把它变成固定要求:
实现时请在文档注释里用 `// ASSUME:` 标出所有未经确认的假设,
并在结尾汇总成清单。不要为了代码完整而自行补齐你没看到的信息。
ASSUME 标记的价值在于它把隐藏的猜测变成可搜索的文本:rg "ASSUME:" 就能拿到这次改动里所有的未验证前提。
7. 哪些任务适合、哪些不适合
| 判断维度 | 适合 | 不适合 |
|---|---|---|
| 是否可验证 | 有测试、有命令能判定对错 | 只有"看起来对"一个标准 |
| 是否本地化 | 改动集中在 1–3 个文件 | 跨 10+ 文件的链路改造 |
| 是否有明确契约 | 接口、数据结构、错误码已定 | 契约本身待设计 |
| 是否可逆 | 能 git reset 回退 |
数据迁移、发布、删除 |
四维全绿:放心交给它。有一项为红:人来主导,它做辅助。
# 典型适合
- 按已有 Controller 模式新增一个接口
- 补单元测试、补参数校验、补日志字段
- 机械式重构:重命名、抽常量、替换已废弃 API
- 写脚本:日志分析、数据核对、批量校验
# 典型不适合
- 决定模块边界与分层方案
- 一次性且不可逆的数据迁移
- 没有测试覆盖的重构
- 排查"偶发、只在生产出现"的问题
最后一条值得展开:偶发问题依赖对真实系统的观察,它看不到你的监控、日志与流量分布,假设常常覆盖不到真正原因。
8. 工具选择的三问
不要按"哪个工具更强"选,按任务形态选。
| 问题 | 回答 | 指向 |
|---|---|---|
| 要不要改文件? | 不改,只想问 | 普通对话 |
| 要不要跑命令、看输出? | 要跑测试/构建/脚本 | CLI 工具(Claude Code / Codex CLI) |
| 要不要长期上下文? | 跨多次任务记住约定 | CLI + AGENTS.md 落盘 |
三个"否":随便一个聊天窗口就够,不必引入工具链。
第二问"是"而第三问"否":单次任务用 CLI,不沉淀文件。
三问全"是":需要约定文件 + 分步工作流的完整配置。
编辑器补全类工具(Cursor 的 tab)与 agent 类工具不是替代关系:前者解决"这一行怎么写",后者解决"这个任务怎么做"。用 agent 做逐行补全,成本高且慢。
9. 排错:判断失误的复盘
| 现象 | 真实原因 | 该怎么改 |
|---|---|---|
| 它把一个简单需求做成了大改造 | 把执行任务当成了设计任务 | 明确"只做最小改动,不要抽新抽象" |
| 输出的架构方案看着合理但用不了 | 让它做架构决策,缺项目历史 | 架构由人定,只让它给候选与代价 |
| 数据迁移出了不可逆错误 | 把不可逆操作交出去了 | 生成脚本,人审阅后执行 |
| 重构后行为变了 | 没有测试覆盖就重构 | 先补特征测试(characterization test)再动 |
| 约定每次都忘 | 只在对话里说过 | 落进 AGENTS.md,不要靠记忆 |
| 越改越偏 | 前提错误还在加提示 | 回退到上一个验证点,重写任务描述 |
倒数第二条最值得投入:凡纠正过两次以上的问题,都该变成一行约定。
10. 边界:这篇讲的范围
| 话题 | 归属 |
|---|---|
| 怎么组织上下文 | 本系列 X09 |
| 生成代码怎么验收 | 本系列 X10 |
| 工作流怎么编排 | 本系列 X11 |
| 具体工具的配置差异 | 按所用版本核对官方文档 |
三篇合起来是一个闭环:把事实讲清楚(X09)→ 把产出验明白(X10)→ 把流程排对(X11)→ 知道哪里不该用它(X12)。
11. 自测
- 四个边界维度中,哪些是硬边界、哪些能用工程手段补?
- "能编译"在验证链条上处于什么位置?至少要跨到哪一档才算验收?
- 为什么它会倾向输出旧版本写法?怎么纠正?
- 安全类失效为什么测试发现不了,防线该设在哪?
- 让模型标注
// ASSUME:的实际作用是什么? - 工具选择三问分别指向什么配置选择?