1. 一个需求做完之后,我才意识到验收才是真正的深水区
用 Claude Code 写完一个 TypeScript 需求,代码跑通了,测试也过了,我原本以为这事就算结了。结果在提交前随手跑了一次git diff --check,屏幕上冒出来一堆尾随空格和行尾不一致的警告,紧接着 CI 上又因为类型定义冲突挂了。那一刻我才真正理解一件事:AI 帮你写代码,快的是生成,慢的是验收。prompt 写得好不好,顶多决定它第一版给你什么;验收做得够不够细,才决定这份代码能不能真正进主干、能不能扛住后面几个月的迭代。
这篇内容我想聊的就是这个被大多数人低估的环节。它适合两类人:一类是刚开始用 Claude Code、TypeScript 做日常开发,觉得"AI 写完就能用"的朋友;另一类是团队里负责代码评审、需要把 AI 产出纳入正式流程的人。我会把整个验收链路拆开讲——从 diff 审查、类型检查、边界验证,到验收资料怎么留痕,尽量给到可以直接抄作业的步骤和参数。核心关键词就几个:prompt、验收、Claude Code、TypeScript、git diff,它们会贯穿全文。
先说结论:AI 生成代码的验收,和传统人工写代码的验收,最大的区别在于信任基线不同。人写的代码,你默认他理解业务;AI 写的代码,你必须默认它只理解了字面,业务语义、边界条件、历史包袱它一概不知。所以验收的重点要从"代码对不对"前移到"它到底理解了什么"。
2. 为什么 AI 写完的代码,验收逻辑要重新设计
2.1 prompt 决定下限,验收决定上限
很多人把精力全砸在 prompt engineering 上,研究怎么把提示词写得又长又准。这没错,但有个认知误区:prompt 只能约束 AI 的输出方向,约束不了输出的正确性。你写"请实现一个用户登录接口,返回 JWT",它可能给你一个能跑的版本,但 token 过期时间、刷新逻辑、异常分支、并发场景,这些它未必按你的预期处理。
我做过一个对比实验。同一个需求,用两版 prompt 让 Claude Code 生成 TypeScript 代码:第一版 prompt 只写功能描述,第二版 prompt 补充了类型约束、错误码规范、边界条件。结果第一版代码能跑但类型全是any,第二版类型完整但多了一个我没要求的缓存层。你看,prompt 优化了,问题并没有消失,只是换了个形式。真正兜住质量的,是后面那套验收动作。
所以我的经验是:prompt 投入产出比在 60 分到 85 分之间最高,剩下 15 分必须靠验收补。指望 prompt 写到 100 分,边际成本高得离谱,而且不稳定。
2.2 AI 代码的三个典型"信任陷阱"
在讲具体验收方法前,先说说 AI 生成代码最容易埋的坑,这些是我踩过多次总结出来的:
- 类型幻觉:TypeScript 项目里,AI 经常写出"看起来类型正确"的代码。比如它声明返回
Promise<User>,但实际某个分支返回了null却没在类型里体现。编译器不报错,运行时才炸。 - 边界缺失:空数组、超长字符串、并发调用、网络超时,这些边界 AI 默认不处理,除非你明确要求。它倾向于写"happy path"。
- 历史包袱无视:项目里已有的工具函数、约定俗成的错误处理方式、特定的目录结构,AI 不知道也不会主动遵守,它按自己的理解重新造轮子。
这三个陷阱决定了验收不能只看"能不能跑",必须有一套结构化的检查清单。
2.3 验收的本质是"重建信任"
我习惯把 AI 代码验收理解成一次"信任重建"过程。AI 生成时,我对它零信任;通过一层层检查,逐步把信任加回来。每一层检查对应一类风险:
| 检查层 | 对应风险 | 主要工具 |
|---|---|---|
| 差异审查 | 改动了不该改的地方 | git diff |
| 格式规范 | 尾随空格、行尾、缩进 | git diff --check |
| 类型检查 | 类型幻觉、隐式 any | tsc --noEmit |
| 逻辑验证 | 边界缺失、分支遗漏 | 单元测试、手动用例 |
| 集成验证 | 与现有代码冲突 | 全量构建、回归测试 |
这张表是我实际验收时的顺序,从便宜到贵,从快到慢。前面几层几分钟就能过,后面几层可能要跑完整测试套件。顺序不能乱,因为便宜的检查能过滤掉大量低级问题,避免你在昂贵的检查上浪费时间。
3. 验收第一关:用 git diff 把 AI 的改动看穿
3.1 为什么 diff 审查是验收的起点
Claude Code 这类工具工作时,往往一次性改动多个文件。它可能重构了一个工具函数、调整了类型定义、顺手改了配置文件。如果你不先看 diff 就直接跑测试,很可能测试过了但引入了你根本没意识到的副作用。
git diff是验收的第一道闸门。我的习惯是 AI 生成完代码后,第一件事不是运行,而是git diff逐文件过一遍。重点看三类改动:
- 预期内的改动:需求直接涉及的文件,正常。
- 预期外的改动:需求没提但被改的文件,必须搞清楚为什么。
- 删除的代码:AI 有时会"顺手清理"它认为没用的代码,这最危险。
3.2 git diff --check 到底在检查什么
热词里出现了git diff --check的作用,这个命令很多人知道但说不清。它的作用是检查即将提交的改动里有没有空白字符问题,具体包括:
- 行尾多余的空格(trailing whitespace)
- 行尾的空白行
- 文件末尾缺少换行
- 行尾符不一致(CRLF vs LF)
为什么这个对 AI 代码特别重要?因为 AI 生成代码时对空白字符不敏感,它可能给你一堆行尾空格,或者把 LF 文件改成 CRLF。这些在本地看不出来,但一旦进 CI,轻则警告,重则因为 lint 规则直接失败。
# 检查工作区改动 git diff --check # 检查已暂存的改动 git diff --cached --check # 检查某个提交 git diff HEAD~1 HEAD --check实测下来,AI 生成的代码里git diff --check报错的概率比人工代码高不少。我现在的流程是:AI 生成完,先git diff --check,有问题先修,再进入下一步。这一步花不了 30 秒,但能省掉后面 CI 挂掉重新排查的半小时。
3.3 差异审查的实操清单
光看 diff 还不够,得有清单。我整理了一份自己常用的 diff 审查清单:
- 有没有改动
package.json、tsconfig.json这类配置文件?改了要确认依赖版本和编译选项没被意外调整。 - 有没有新增依赖?新增的依赖是否必要,版本是否合理。
- 有没有删除已有函数或导出?删了要确认没有其他地方引用。
- 类型定义有没有被放宽?比如
string变成any,User变成unknown。 - 有没有引入新的全局变量或副作用?
提示:审查 diff 时建议用
git diff --stat先看改动概览,再逐个文件细看。改动文件超过 10 个时,优先看配置文件和类型定义文件,它们的影响面最大。
4. TypeScript 项目的类型验收:别被"能编译"骗了
4.1 tsc --noEmit 是底线不是全部
TypeScript 项目验收,第一反应是跑tsc --noEmit。这没错,但要知道它的局限:它只能验证类型系统内部的一致性,验证不了类型和运行时行为是否匹配。
# 基础类型检查 npx tsc --noEmit # 严格模式检查(推荐) npx tsc --noEmit --strict # 检查未使用的变量和参数 npx tsc --noEmit --noUnusedLocals --noUnusedParametersAI 生成的代码经常能通过tsc --noEmit,但存在类型幻觉。比如它写:
async function getUser(id: string): Promise<User> { const res = await fetch(`/api/user/${id}`); const data = await res.json(); return data; // data 是 any,被隐式断言成 User }这段代码类型检查能过,因为res.json()返回any,any可以赋值给任何类型。但运行时如果接口返回错误结构,data根本不是User,调用方拿到的就是脏数据。类型检查过了,不代表类型是对的。
4.2 类型验收的三个深水区
我在 TypeScript 项目里验收 AI 代码,重点盯三个地方:
第一,隐式 any 和类型断言。搜索代码里有没有as断言、有没有any、有没有@ts-ignore。AI 为了"让代码编译通过",很爱用这些手段。每一个as都要问:这里为什么需要断言?能不能用类型守卫替代?
第二,可选链和空值处理。AI 经常写user?.name但后面又直接user.name,逻辑不一致。或者声明返回User | null,但调用方没处理null。
第三,泛型和类型工具的使用。热词里提到vue 类型工具与现有 typescript 7 不兼容、typescript 命名空间 declare global,这些是真实项目里常见的类型冲突点。AI 生成泛型代码时,经常写出约束过宽或过窄的类型,导致和其他模块不兼容。
4.3 类型验收的实操方法
我的做法是分三步:
- 静态扫描:用 grep 或 IDE 搜索
any、as、@ts-ignore、@ts-expect-error,逐个审查。 - 严格模式编译:确保
tsconfig.json里strict: true,然后tsc --noEmit。 - 类型测试:对关键类型写类型测试,用
expectType之类的工具验证类型推导结果。
// 类型测试示例 import { expectType } from 'tsd'; // 验证 getUser 返回类型 expectType<Promise<User>>(getUser('123')); // 验证错误分支类型 expectType<Promise<User | null>>(getUserSafe('123'));这一步很多人跳过,但对 AI 代码特别值得做。因为 AI 的类型推导经常和你的预期有偏差,类型测试能把这个偏差显性化。
注意:如果项目里用了
declare global扩展全局类型,AI 生成的代码可能和现有全局声明冲突。验收时务必检查global.d.ts或类似文件有没有被改动。
5. 逻辑与边界验收:AI 最不擅长的地方
5.1 为什么边界条件是 AI 的盲区
AI 生成代码时,训练数据里"正常路径"的代码远多于"边界处理"的代码。所以它默认写 happy path,边界条件要么不写,要么写得敷衍。我见过 AI 写的分页逻辑,完全没处理pageSize为 0 或负数的情况;也见过它写的数组处理,没考虑空数组。
验收逻辑时,我的核心方法是主动构造边界用例,而不是等测试报错。具体来说,针对每个函数问自己:
- 输入为空、为 null、为 undefined 时会怎样?
- 输入超长、超大时会怎样?
- 并发调用时会怎样?
- 依赖的外部服务失败时会怎样?
5.2 单元测试是验收的放大器
AI 生成代码后,我通常会让它同时生成单元测试,但不会直接信任这些测试。原因很简单:AI 写的测试往往只覆盖它自己写的逻辑,边界用例它想不到,你也就测不到。
我的做法是:AI 生成测试作为基础,然后我手动补充边界用例。补充的重点就是上面那四类问题。补充完之后,跑覆盖率,看哪些分支没被覆盖。
# 运行测试并生成覆盖率报告 npx jest --coverage # 只跑改动的文件相关测试 npx jest --findRelatedTests src/user.ts覆盖率不是越高越好,但关键路径的分支覆盖率必须看。AI 代码里那些if/else的 else 分支,经常是空的或者直接 throw,这些地方就是风险点。
5.3 集成验收:别让 AI 代码成为孤岛
单个函数验收通过,不代表集成没问题。AI 生成的代码可能:
- 用了和项目不一致的错误处理方式
- 引入了循环依赖
- 改了共享类型导致其他模块编译失败
- 新增的依赖和现有依赖版本冲突
集成验收我一般跑三件事:全量构建、全量测试、关键路径手动验证。全量构建能发现类型和依赖问题,全量测试能发现回归,手动验证能发现那些测试覆盖不到的交互问题。
| 验收类型 | 命令示例 | 发现问题类型 |
|---|---|---|
| 全量构建 | npm run build | 类型冲突、依赖缺失 |
| 全量测试 | npm test | 回归、集成失败 |
| 手动验证 | 启动应用走关键流程 | 交互、体验问题 |
6. 验收资料留痕:让验收过程可追溯
6.1 为什么要留验收资料
热词里出现了服务器安装记录 记录验收资料、项目验收系统概要设计说明书,说明验收留痕是个真实需求。对 AI 代码来说,留痕尤其重要,因为你需要证明这段代码经过了人工审查,而不是直接信任 AI 输出。
留痕的内容包括:diff 审查记录、类型检查结果、测试覆盖率、手动验证步骤和结果。这些资料在代码评审、问题回溯、责任界定时都用得上。
6.2 验收资料的实操模板
我习惯在 PR 描述里附一份验收清单,格式大概是这样:
## AI 代码验收记录 ### 生成信息 - 工具:Claude Code - prompt 摘要:实现用户登录接口,返回 JWT - 生成时间:2024-XX-XX ### 验收检查 - [x] git diff --check 通过 - [x] tsc --noEmit --strict 通过 - [x] 单元测试通过,覆盖率 85% - [x] 手动验证登录、登出、token 过期场景 - [x] 无新增未审查依赖 ### 遗留问题 - token 刷新逻辑待补充 - 并发登录场景未覆盖这份记录看起来繁琐,但实际写起来五分钟,能省掉后面扯皮的两小时。而且它逼着你把验收做完整,不能糊弄。
6.3 把验收清单固化到流程里
个人开发靠自觉,团队开发靠流程。我建议把 AI 代码验收清单固化到 CI 里,能自动化的自动化:
git diff --check放进 pre-commit hooktsc --noEmit放进 CI- 测试覆盖率阈值卡在 CI 里
- PR 模板里加验收清单勾选项
这样即使有人想偷懒,流程也会拦住。验收不是靠人记得做,而是靠流程保证必须做。
7. 常见问题与排查技巧实录
7.1 AI 代码验收常见问题速查
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| CI 报尾随空格 | AI 生成时未清理空白 | git diff --check |
| 类型检查过但运行报错 | 类型幻觉、隐式 any | 搜索as、any |
| 测试过了但线上出问题 | 边界未覆盖 | 补充边界用例 |
| 改动文件超出预期 | AI 顺手重构 | git diff --stat审查 |
| 依赖版本冲突 | AI 新增依赖 | 检查package.json |
| 全局类型冲突 | declare global被改 | 检查.d.ts文件 |
7.2 几个我踩过的坑
坑一:信任 AI 写的测试。早期我让 AI 生成代码和测试,测试全绿就提交。结果线上发现一个空数组导致的崩溃,而 AI 的测试里根本没测空数组。教训:AI 的测试只能作为起点,边界用例必须自己补。
坑二:忽略 git diff --check。有次提交后 CI 挂了,排查半天发现是行尾符问题。AI 生成的文件用了 CRLF,项目要求 LF。从那以后git diff --check成了我的固定动作。
坑三:类型断言埋雷。AI 写了个data as User,当时类型检查过了,我没细看。后来接口返回结构变了,data根本不是User,但类型系统没报错,运行时才炸。教训:每一个as都要问为什么。
坑四:prompt 太长导致截断。热词里有prompt is too long,我也遇到过。prompt 写太长,AI 反而抓不住重点,生成质量下降。后来我学会把需求拆成小块,分步生成,每步验收。
7.3 验收效率的提升技巧
验收虽然重要,但不能无限投入。我的效率技巧:
- 分层验收:便宜的检查先跑,贵的检查后跑,快速过滤。
- 自动化优先:能进 CI 的绝不手动。
- 重点盯类型和边界:这两块是 AI 代码的重灾区。
- 建立个人检查清单:把踩过的坑变成清单项,下次直接对照。
提示:验收时间控制在生成时间的 1 到 2 倍比较合理。如果验收时间远超生成时间,说明 prompt 或需求拆分有问题,要回头优化。
8. 我个人的一点体会
用 Claude Code 这类工具做 TypeScript 开发,最大的认知转变是:AI 把写代码的成本降下来了,但把验收的成本顶上去了。以前写代码慢,但写的时候脑子里就在做验收;现在生成快,验收变成了一个独立的、必须刻意执行的环节。
我现在的习惯是,AI 生成完代码,先不急着高兴,先跑git diff --check,再看 diff,再跑类型检查,再补边界测试。这一套下来,一个中等需求的验收大概二十分钟。听起来不少,但比起线上出问题再回滚排查,这二十分钟花得值。
最后分享一个小技巧:把每次验收发现的问题记下来,攒成自己的"AI 代码避坑清单"。用不了多久你就会发现,AI 犯的错其实就那么几类,清单越来越短,验收越来越快。验收这件事,本质上是在和 AI 的"平均水准"博弈,你越了解它的短板,验收就越有的放矢。