【万字】实战报告:AI Coding 已经能做交付了,但前提苛刻
当业界还在争论“AI 能否写出高质量代码”时,2026 年的实战验证已将命题推向了更深层的维度:AI 能否独立承担从需求理解、工程初始化、迭代开发到自动化验收的完整交付闭环? 本文通过构建脱敏演示项目,在“从 0 到 1 新建”、“存量项目迭代”与“老旧项目规范重构”三大真实场景中深度实测,揭示了 AI Coding 能力的边界真相——它不再是单纯的代码生成器,而是一个需要被结构化输入、明确边界约束与严格验收标准所定义的“协作伙伴”。

首先,为什么我想认真做这次 AI Coding 验证?
现在大家对 AI Coding 已经不陌生了,代码补全、写工具函数、起一个页面、做一个 Demo,这些事情它大多都已经能做,而且很多时候做得还不错。
所以如果只是讨论“AI 会不会写代码”,这个问题其实已经没有太多讨论价值了。至少在局部编码这件事上,它的可用性基本已经被反复验证过了。
但我一直更想确认的是另一件事:
如果把问题往前和往后都拉长一点,不再只是让它补某一段代码,而是让它真正进入一段完整的交付过程。
从理解需求开始,到实现功能,再到验证和收口,那它现在到底能做到什么程度?
换句话说,我这次真正想验证的,不是 AI 能不能辅助写代码,而是它有没有可能从局部参与走到完整参与交付。
这个问题,看起来只差一点,实际差得很远。
局部参与,验证的是它的编码能力;完整参与交付,验证的其实是它在一整段任务里的稳定性:AI Coding 能不能理解边界,能不能按要求推进,能不能在约束下把事情做完,或者只是产出几段看起来还行的代码。

为了把这个问题看得更清楚一点,我没有直接拿现有业务项目来试。
一方面,业务项目本身不适合公开展示;另一方面,我也不希望把业务复杂度、历史包袱和真实约束全部搅在一起,最后很难判断,到底是 AI 的能力到了边界,还是测试条件本身不够干净。
所以这次我专门搭了一个脱敏的演示项目,并刻意把验证拆成了三个场景:
- 第一个场景,是从 0 到 1,直接让 AI 按要求搭一个新项目,并完成一个最小功能;
- 第二个场景,是在已经搭起来的项目里继续加需求,模拟更接近日常开发的迭代过程;
- 第三个场景,则是模拟一个文档不全、约束不清的老项目,先补规范,再继续做功能;
这三个场景连起来,基本就是我想验证的那条完整问题链:AI 今天到底能不能真正参与一段完整交付,而不只是把局部编码做得更快?

如果先给结论,我现在的判断是:AI Coding 已经不只是适合写 Demo、补函数、起页面了。
在约束清楚、边界明确、验收方式可执行的前提下,它已经可以参与相当一部分真实交付工作
但这个结论不是无条件成立的。从这次实践看,它更适合三类项目场景:
- 新项目:适合先搭基线,再落最小功能
- 结构相对清楚的已有项目:适合在边界明确的前提下继续迭代
- 约束缺失但仍可运行的老项目:适合先补最小规范,再继续往下做
如果换一种更直接的说法,我现在会把 AI 编程的可用范围理解成这样:
- 做 Demo、局部编码、小功能实现:已经比较成熟
- 做新项目初始化和最小功能闭环:可以承担
- 做已有项目中的小到中等规模迭代:可以承担,但前提是边界要先框清楚
- 做老项目直接功能开发:不建议直接开始,更适合先补规范、再做功能做高不确定性、强业务耦合、约束
- 大量依赖隐性经验的任务:仍然不够稳
也就是说,它已经可以参与交付,但更像一个需要被定义清楚、被约束好、被验证到位的协作对象,而不是一个把需求丢过去就能自动收口的全能开发者。
下面这三个场景,就是我这次为了验证这个判断,专门拆出来的一条完整实验链路。
一、输入 > 模型
——真正影响结果的,往往不是模型,而是输入
刚开始做这次实践的时候,我的想法其实也比较直接:既然是想验证 AI 能不能承担更多工作,那最自然的方式,就是把任务交给它,让它尽量往下做,我再回来检查、修正和收尾。

说得简单一点,就是先看看它到底能接住多少。这种用法在很多小任务上其实没有问题,甚至会让人觉得很顺。
尤其是那些上下文比较简单、边界也比较清楚的事情,比如补一个函数、写一个局部页面、改一个相对独立的小功能,AI 往往能很快给出一个还不错的结果。
也正因为如此,最开始的时候,很容易形成一种判断:好像只要需求描述得差不多,后面的事情就可以交给它了。
但等任务稍微完整一点,问题就开始慢慢冒出来:
- 有时候是功能虽然做出来了,但一些关键细节和预期并不一致;
- 有时候是代码能跑,但实现方式和项目原本的组织方式并不统一;
- 还有一些情况是,你以为理所当然的前提,其实根本没有写出来,于是 AI 会自己去补全,而它补出来的东西未必就是你真正想要的;
后来我慢慢意识到,这里面最麻烦的地方,往往并不是“它把代码写错了”,而是很多偏差其实在写代码之前就已经出现了。
更准确地说,很多问题发生在“理解任务”这一步。
当任务只是局部编码时,这个问题还没那么明显。因为上下文很短,目标也比较集中,AI 就算理解得不够完整,影响范围通常也有限。
但一旦任务开始变长,开始涉及需求边界、功能约束、验证标准、回退方式这些东西,输入本身如果不完整,后面就很难稳定。
也就是从这个时候开始,我越来越确定:决定结果的关键,不只是模型,而是我们到底给了它什么样的输入。
如果输入只是一个方向性的描述,那 AI 很多时候做的,其实不是执行,而是在猜。它一边写代码,一边补我没有明确说出来的前提。
而问题恰恰在这里:那些没被写出来的前提,很多时候才是真正决定结果稳不稳的东西。
所以后来我调整的重点,不再是“怎么把提示词写得更花”,而是另一件更基础的事情:怎么把任务定义得更完整一点。
我开始不满足于只给它一个需求描述,而是尽量把目标、边界、规则、验收方式这些内容提前写清楚,让它面对的不是一个模糊任务,而是一份相对明确的执行说明。
我后来会把模糊描述和结构化任务定义放在一起看,差异会非常直观:

左边这种描述,人能靠经验补全很多前提;但放到 AI 面前,往往就会变成大量默认假设。
右边这种写法看起来更“啰嗦”,但真正能明显提升稳定性的,通常恰恰是这些被显式写出来的边界、规则和验收条件。
做完这次实践之后,我越来越确定,真正影响结果的,往往不是模型本身,而是你给它的输入,到底是不是一份足够清楚的任务定义。
二、去配合模型
——如果想让 AI 参与交付,需求就不能只写给人看
以前写需求,更多是为了让人看懂。业务目标说清楚,流程大致讲明白,关键页面和规则列出来,通常也就可以往下推进了。
很多默认前提不一定会专门写出来,因为团队里的人大多知道背景,也知道哪些地方该怎么处理。即便文档里没写得特别细,很多信息也能在协作过程中慢慢补齐。
但这次实践让我越来越明确地感受到,AI 面对的不是这样的协作环境。
人和人协作时,很多东西是可以靠经验、上下文和来回沟通补上的。但 AI 不一样。它能依赖的,基本就是你明确给到它的那部分信息
这就意味着,以前那些“默认大家都知道”的内容,放到 AI 面前,其实很多都应该重新写出来。否则它只能根据现有输入去猜,而一旦进入猜的阶段,结果就会变得不稳定。
所以后来我开始要求自己,把需求写得更具体一些:
- 不只是写“这次要做什么”,还要写“哪些事情这次不做”;
- 不只是写功能本身,还要写规则和边界;
- 不只是写页面和交互,还要写最后怎么验证算完成;
- 如果有上线风险,还要提前考虑有没有回退空间;
说得更直接一点,我后来更希望需求文档像一份能直接执行的说明,而不只是一个方向性的描述。
比如这次实践里,其中一个很小的功能,是新增一个用户表单。
如果只是写一句“做一个新增用户表单页面”,那这个任务其实非常松。字段怎么设计、校验做到什么程度、有没有开关控制、要不要补测试、做到什么算完成,这些全都没有边界,AI 只能自己去补。
但如果把这些前提往前补一点,事情就会清楚很多:
## 目标
在当前项目中实现一个“新增用户表单”功能,并通过 feature flag 控制新表单是否启用。
## 校验规则
### name
– 必填
– 长度 2 到 20
– 必填
– 必须符合邮箱格式
### role
– 必填
– 枚举值固定为:
– `admin`
– `editor`
– `viewer`
## Feature Flag 要求
新增 `newUserForm` 开关,并满足:
– 默认关闭
– 开启时展示新表单
– 关闭时展示占位内容或旧版占位区域
feature flag 要显式、易定位。
## 测试要求
至少补充以下测试:
1. feature flag 开关对应的渲染分支
2. 表单校验错误展示
如果实现方式合理,可补更多,但不要为了数量堆测试。
## 验收标准完成后至少满足:
1. 新表单可渲染
2. 校验规则生效
3. feature flag 生效
4. `pnpm lint` 通过
5. `pnpm test` 通过
6. `pnpm build` 通过
PS:其实从这里大家就可以直观感受到了,代码不再是必须,自然语言就是代码
这也是我这次实践里最深的一个感受之一:
如果目标只是让 AI 帮忙写点代码,那以前那种偏描述性的需求文档也许还能凑合;但如果目标是让它真正参与交付,需求本身就不能只写给人看。
需求一旦写清楚,AI 不一定会突然变得更“聪明”,但它的表现通常会明显稳定很多。
三、从0到1
——拿一个新项目做验证
第一个场景,我故意从一个最“空”的状态开始。
原因很简单。如果 AI 连从 0 到 1 都接不住,后面讨论它怎么参与已有项目的迭代,其实意义并不大。因为那种情况下,它更多还是在已有上下文里“顺着写”,而不是在真正承担一段完整的起步工作。
而且从 0 到 1 还有一个好处:很多问题会暴露得更直接。
没有现成目录,没有既有约定,也没有默认上下文。这种情况下,如果要把一个项目真正搭起来,AI 面对的就不只是“写代码”本身,而是要先处理一层更基础的东西:项目结构怎么组织、脚本怎么约定、验证方式怎么建立、后面还能不能继续迭代。
所以这个场景里,我一开始并没有把目标设成“让它快速生成一个页面”,而是先给自己定了一个更明确的判断标准:这个项目至少要像一个能继续发展的项目,而不只是一个跑起来的壳子。
也就是说,它要满足的不只是“能启动”,还包括:
- 结构要足够清楚
- 脚本要统一
- 基本验证要能跑通
- 后面还能继续往里加需求
- 整个项目要适合拿来做后续两个场景的继续验证
从这个角度看,我要验证的其实不是“AI 会不会初始化一个 Vite 项目”,而是: 在没有现成上下文的情况下,它能不能按照要求,把项目先搭到一个可继续交付的状态。
所以这个阶段,我最先写的不是业务功能,而是一份初始化规格:
## 目标
基于以下技术栈初始化一个现代化前端项目:
– pnpm
– Vite
– React
– TypeScript
– Biome
– Vitest
– React Testing Library
– Husky
– Commitlint
项目初始化后,应具备继续承接后续功能迭代的能力。
## 本次要做的事
1. 初始化项目
2. 建立清晰的基础目录结构
3. 配置 Biome
4. 配置 Vitest 与 React Testing Library
5. 配置 Husky
6. 配置 Commitlint
7. 补齐基础 scripts
8. 提供最小可用的 README
9. 提供至少 1 条可运行的样例测试
## 目录要求
优先建立以下结构:
– `src/app/`
– `src/pages/`
– `src/components/`
– `src/lib/`
– `src/tests/`
– `spec/`目录不要过度设计,也不要创建无用目录。
## 脚本要求
至少具备以下命令:
– `pnpm lint`
– `pnpm format`
– `pnpm test`
– `pnpm build`
– `pnpm dev`
## README 要求
README 至少说明:
– 项目如何安装依赖
– 项目如何启动
– 如何执行 lint / test / build
– 基础目录结构说明
## 验收标准完成后至少满足:
1. `pnpm lint` 可执行
2. `pnpm test` 可执行
3. `pnpm build` 可执行
4. README 可读
5. 目录结构清晰
这份规格里没有太多复杂内容,主要就是把一些项目级别的约束先说清楚,比如技术栈、目录结构、脚本要求、测试基线,以及 README 至少需要说明哪些东西。
换句话说,我希望 AI 面对的不是一句“帮我起个项目”,而是一份更明确的初始化要求。
这个动作看起来有点“慢”,但后来回头看,它其实非常关键。
因为从 0 到 1 的场景里,最容易出的问题往往不是代码写错,而是方向一开始就偏了。
比如依赖引得太多、目录切得太散、脚本不统一、测试环境缺失,或者为了图快临时拼了一套不太能继续维护的结构。短期看它们都不是大问题,但如果一开始没有收住,后面每加一个需求,都会不断放大这些问题。
所以在真正让它动手之前,我没有直接要求它开始改代码,而是先让它把计划说出来:

我会先看:
- 它准备怎么拆步骤
- 它觉得要新增或修改哪些文件
- 依赖会不会加得过多
- 它准备怎么验证结果
这个阶段,我关注的重点不是“它写得快不快”,而是“它理解得对不对”。
因为很多偏差,其实在真正写代码之前就已经出现了。如果方向一开始偏了,后面代码写得再认真,也只是沿着一个不够理想的方向越走越远。
等这些都看起来没问题了,后面的实现反而是顺下来的。到了这个阶段,我真正关心的,也不再是它一共写了多少代码,而是最后这些东西能不能顺利通过验证。

$ pnpm lint
…
Checked 6 files in 2ms. No fixes applied.
$ pnpm test
…
Test Files 1 passed (1)
Tests 2 passed (2).
..
$ pnpm build
…
✓ built in 397ms
这个场景做到这里,其实只能证明一半:AI 已经能把项目基线搭起来,并把最基本的工程约束先立住。
但如果只停在这里,还不能完全说明它已经具备继续交付功能的能力。
所以项目基线搭起来之后,我没有马上进入第二个场景,而是先在这个新项目里落了一个最小功能:新增用户表单。
这一步很重要,因为如果只做到 bootstrap,其实还只能说明 AI 能把项目框架搭起来,还不能完全说明它已经具备继续交付功能的能力。
只有当它能在这个基线上继续完成一个具体功能,并把校验、开关、测试这些细节一起接住时,场景一才算真正闭环。
所以我给它的第一个功能,不是很复杂,但故意保留了几个很“像真实项目”的要素:
- 表单字段与校验规则
- feature flag
- 成功态提示
- 最低限度的测试要求
- 明确的验收命令
## 功能要求
新增一个用户表单,包含以下字段:
– `name`
– `email`
– `role`
## 校验规则
### name
– 必填
– 长度 2 到 20
– 必填
– 必须符合邮箱格式
### role
– 必填
– 枚举值固定为:
– `admin`
– `editor`
– `viewer`
## 交互要求
– 展示字段标签
– 展示校验错误信息
– 提供提交按钮
– 提交可使用 mock 逻辑,不接后端
– 提交成功后有明确成功提示
## Feature Flag 要求
新增 `newUserForm` 开关,并满足:
– 默认关闭
– 开启时展示新表单
– 关闭时展示占位内容或旧版占位区域
feature flag 要显式、易定位。
## 测试要求
至少补充以下测试:
1. feature flag 开关对应的渲染分支
2. 表单校验错误展示
如果实现方式合理,可补更多,但不要为了数量堆测试。
## 验收标准
完成后至少满足:
1. 新表单可渲染
2. 校验规则生效
3. feature flag 生效
4. `pnpm lint` 通过
5. `pnpm test` 通过
6. `pnpm build` 通过
从实际改动文件看,这次实现也基本控制在了一个很小的范围内:页面层负责接住功能入口,组件层负责表单本身,lib 层负责规则和开关,测试则分别覆盖页面分支和表单行为。

其中一个我刻意要求保留的点,是 feature flag。因为我不想验证的只是“页面能不能做出来”,而是“这个能力是否具备最小回退空间”。
// featureFlags.ts
export const featureFlags = {
newUserForm: false,
}
校验逻辑我也要求尽量集中,而不是散落在组件内部。这样一方面更贴近真实项目的写法,另一方面也更容易测试和复查。
// validation.ts 核心代码
export const roleOptions = [‘admin’, ‘editor’, ‘viewer’] as const
exportfunction validateUserFormValues(values: UserFormValues): UserFormErrors {
const nameRequiredError = validateRequired(values.name, ‘姓名为必填项’)
const emailRequiredError = validateRequired(values.email, ‘邮箱为必填项’)
const roleRequiredError = validateRequired(values.role, ‘角色为必填项’)
const nameError =
nameRequiredError ??
validateLengthRange(values.name, 2, 20, ‘姓名长度需在2到20个字符之间’)
const emailError =
emailRequiredError ?? validateEmailFormat(values.email, ‘邮箱格式不正确’)
const roleError =
roleRequiredError ??
validateOneOf(values.role, roleOptions, ‘角色不合法’)
return {
name: nameError,
email: emailError,
role: roleError,
}
}
测试这一步,我没有要求它堆很多数量,而是要求它把关键行为覆盖清楚。
页面层的测试主要验证 feature flag 开关前后的渲染分支:开关关闭时显示占位内容,开启时显示新表单。表单层的测试则覆盖了必填校验、长度和邮箱格式校验、非法角色值,以及最终提交成功提示。
it(‘renders placeholder when newUserForm flag is off’, () => {})
it(‘renders new user form when newUserForm flag is on’, () => {})
it(‘shows required errors when submitting empty form’, () => {})
it(‘shows success message after valid submission’, () => {})
做到这里,其实已经足以说明一件事:只要规格写得足够清楚,AI 接住的就不只是页面本身,还包括规则、校验和测试这些过去往往需要人工补齐的部分。
但我还想再往前推一步。
项目基线和第一个最小功能都跑通之后,我没有只停留在单测和构建通过,而是又加了一层自动化验收:要求 Claude Code 借助 Playwright CLI,从用户视角把关键流程再跑一遍。
这一步对我来说很重要。因为如果只看到代码、单测和构建结果,虽然已经能证明功能基本成立,但它仍然更偏“工程内部视角”。
而我这次想验证的是,AI 能不能更完整地参与交付,那它就不应该只负责把代码写出来,还应该尽可能把“功能到底有没有真的跑通”这件事一起验证掉。
所以我给 Playwright 的范围控制得很小,只覆盖关键路径:
- 空表单提交时是否能展示必填校验错误
- 非法输入时是否能展示对应错误
- 合法输入后是否能出现成功提示
这一步做完后,我对场景一的判断才真正完整起来:
从 0 到 1,AI 不只是能把项目基线搭起来,也不只是能把第一个功能写出来,而是已经能够借助自动化工具,把这个功能从实现一路推进到最基本的验收。
如果一开始只是追求“先跑起来”,那很容易得到一个表面很快、后面却越来越难接着做的项目。反过来,如果先把项目级别的规则说清楚,再让 AI 往下执行,并在最后补上一层真正从用户视角出发的自动化验收,整个过程会稳很多。
四、难点:加需求
——项目搭起来之后,真正难的是继续往里加需求
第一个场景做完之后,项目至少已经站住了。基线有了,最小功能也有了,单测、构建和一轮自动化验收也都跑通了。
做到这里,已经足以说明 AI 不只是会“起一个项目”,也不只是会“写一个页面”,而是已经能在明确约束下,把一段从初始化到最小验收的链路接起来。
但这还不够。因为真实开发里,大多数时候并不是从 0 开始,而是在一个已经存在的项目里不断往下加需求、补功能、做调整。
这也是第二个场景更接近日常开发的地方。
项目已经有了基础结构,页面也已经起来了,测试和构建也都在。这个时候继续往里加功能,表面上看比从 0 到 1 更简单,但实际上,真正麻烦的地方反而开始出现了。
因为在已有项目里,问题的重点往往不再是“能不能写出来”,而是“会不会把原来的东西带乱”。
这类场景里最容易出现的问题,大概有三种。
- 改动范围失控。本来只是一个不大的需求,但做着做着会牵出越来越多修改;
- 顺手碰了不该碰的地方,为了实现这次需求,把原有结构也一起改了;
- 功能虽然加进去了,但实现方式和项目原本的组织方式并不一致,表面上完成了,实际上留下了新的不协调。
所以到了这个阶段,我自己的使用方式会有一个明显变化。
在场景一里,我更关心的是“它能不能先把项目立起来”; 但到了场景二,我先问的就不再是“怎么实现”,而是“会改哪些地方”:
## 目标
在现有项目基础上新增一个用户列表页面,用于模拟已有项目中的功能迭代。
## 功能要求
1. 展示 mock 用户列表
2. 提供搜索输入框
3. 支持按 `name` 或 `email` 过滤
4. 无结果时展示空态
## 本次任务重点
这个任务的重点不只是实现列表,而是控制改动边界。开始实现前,需要先明确:
– 会新增哪些文件
– 会修改哪些文件
– 改动边界在哪里
– 风险点是什么不要顺手调整无关结构。
## 输出要求
开始改动前,先输出:
1. 文件改动清单
2. 实现计划
3. 风险与边界说明
4. 验证命令
也就是说,我不会让它一上来就直接给我代码,而是会先要求它把边界说清楚:
- 这次会影响哪些页面或模块
- 哪些文件需要新增,哪些文件需要修改
- 改动范围大概有多大
- 风险点主要在哪里
- 最后准备怎么验证
这个动作非常重要。因为在已有项目里,很多问题不是功能本身做错了,而是本来一个很小的需求,最后却牵扯出一片不必要的改动。范围一旦失控,后面的验证成本就会明显上升,整个过程也更难收口。
换句话说,在已有项目里,我更关心它打算动哪里,而不是它打算怎么写。
这背后其实是一个很现实的考虑:如果边界先清楚了,后面的过程通常都会稳很多。你知道它准备碰什么地方,也知道哪些地方最好不要动,那么后面的验证、回看和继续迭代,都会容易很多。
反过来,如果一开始没有把边界框出来,AI 很容易凭它自己的理解去“顺手优化”一些东西。单看每一处修改,可能都不算离谱,但整体上很容易让结构慢慢变形。尤其是在一个已经有既有做法的项目里,这种“顺手改一点”的累计代价会非常高。
这次我在已有项目上新增的是一个用户列表页,功能本身并不复杂,主要包括:
- 展示 mock 用户数据
- 支持按姓名或邮箱搜索
- 搜索无结果时展示空态
这个功能刻意选得比较克制。因为我的目标不是用一个复杂需求去证明 AI 有多强,而是想验证:在一个已经成型的项目里,它能不能在边界明确的前提下,继续把事情做稳。
从最终落地结果看,这次改动基本也保持在了一个比较小的范围内:
App.tsx 只做了最小的页面切换入口,用 useState 在“创建用户”和“用户列表”两个页面之间切换;
UserListPage.tsx 作为独立页面承接列表、搜索框和空态展示;
mockData.ts 和 userFilter.ts 则把数据和过滤逻辑放回到了 lib 层,没有直接揉进页面组件里。

这一步让我比较满意的一点,不是它把列表做出来了,而是它没有为了一个小需求去撬动原有结构。
它没有顺手引入路由库,也没有借机扩展分页、排序、编辑删除这些 spec 里根本没要求的内容。整个改动看起来更像一次正常的功能迭代,而不是一次失控的“顺便升级”。
从页面代码本身看,这种边界感也比较明显:App.tsx 只负责页面切换和渲染,不承载列表业务逻辑。
例如这次实际就是用一个很轻的本地状态做页面切换:
// App.tsx
type Page = ‘create’ | ‘list’
const [currentPage, setCurrentPage] = useState<Page>(‘create’)
{currentPage === ‘create’ ? <UserCreatePage /> : <UserListPage />}
而列表数据和过滤规则也没有直接写进页面里,而是都放在了 lib 层。 数据本身只是一个很轻的 mock 集合,但结构已经是清楚的:
export interface User {
id: string
name: string
email: string
role: string
}
export const mockUsers: User[] = [
{ id: ‘1’, name: ‘张三’, email: ‘zhangsan@example.com’, role: ‘admin’ },
{ id: ‘2’, name: ‘李四’, email: ‘lisi@example.com’, role: ‘user’ },
{ id: ‘3’, name: ‘王五’, email: ‘wangwu@example.com’, role: ‘admin’ },
{ id: ‘4’, name: ‘赵六’, email: ‘zhaoliu@example.com’, role: ‘viewer’ },
]
对应的过滤逻辑也被单独抽成了纯函数:
// userFilter.ts
export function filterUsers(users: User[], query: string): User[] {
if (!query.trim()) {
return users
}
const lowerQuery = query.toLowerCase()
return users.filter(
(user) =>
user.name.toLowerCase().includes(lowerQuery) ||
user.email.toLowerCase().includes(lowerQuery),
)
}
这一点很关键。因为它意味着这次迭代不是简单把功能“糊上去”,而是尽量沿着已有结构,把页面职责、数据职责和规则职责分开。这种分层看起来并不复杂,但它会直接影响后面这个项目还能不能继续往下演化。
测试这一步,我也仍然保持了和前面一样的思路:不追求数量,而是优先覆盖关键行为。
这一轮最重要的测试点其实很直接:
- mock 用户列表是否能正常渲染
- 按姓名搜索时过滤逻辑是否生效
- 按邮箱搜索时过滤逻辑是否生效
- 当没有匹配结果时,空态是否会正确出现
// UserListPage.test.tsx 测试点摘要
it(‘renders user list with mock data’, () => {})
it(‘filters users by name’, () => {})
it(‘filters users by email’, () => {})
it(‘shows empty state when no results’, () => {})
另外,这次我还额外保留了一层更细的验证:把过滤逻辑单独抽出来后,又给它补了独立测试。这样一来,页面层验证的是“用户能看到什么”,规则层验证的是“过滤逻辑本身对不对”,两层职责会更清楚。
// userFilter.test.ts 测试点摘要
it(‘returns all users when query is empty’, () => {})
it(‘filters by name’, () => {})it(‘filters by email’, () => {}
)it(‘returns empty array when no match’, () => {})
it(‘is case insensitive’, () => {})
完成后,我依然会要求它输出一份简短的交付说明,把这次到底改了什么、风险点在哪里、验证方式是什么,统一交代清楚。
从这次 AI 给出的总结里,信息其实已经比较完整了:新增了 mock 数据和过滤工具函数,新增了用户列表页和测试,App.tsx 只做了最小页面切换,最终 pnpm lint、pnpm test 和 pnpm build 都通过了。

这一点看起来只是“补一段说明”,但它其实很关键。
因为在已有项目里,真正接近交付的,不只是代码本身,还包括你能不能把这次改动讲清楚。
改了哪些地方、影响范围多大、验证到什么程度,这些信息如果不能被稳定产出,就很难说它已经真的进入了交付链路。
所以第二个场景做下来,我最大的感受是:在已有项目里,AI 的可用性并不只取决于它写得快不快。 更重要的是,你有没有先把边界框出来。
边界一旦清楚,验证方式也提前明确,它在存量迭代里的表现会稳定很多。 某种意义上说,AI 真正开始变得“能用”,不是因为它更会写了,而是因为它开始能在边界里做事。
这也是我对场景二最核心的判断: 从 0 到 1,关键是先立约束;而在已有项目里继续往下走,关键则是先控边界。
五、最后问题:老项目
——老项目最先要补的,不是功能,而是约束
如果只做前两个场景,这次验证其实还不完整。
因为新项目也好,相对干净的存量项目也好,本身都还带着一种“理想环境”的前提:结构至少是清楚的,约束至少是写得出来的,验证方式至少能补得上。
但真实世界里,很多项目其实并不是这样。
真正更常见的情况是:项目已经跑了很久,功能不少,代码也不一定不能用,但很多最基础的东西其实是缺的。文档不全,脚本不统一,测试没有形成基线,目录结构也未必一致,很多规则都存在于人的脑子里,而不是写在项目里。
所以第三个场景我一定要单独测。 因为对很多团队来说,这才是更接近现实的问题。
而且这种项目真正麻烦的地方,往往并不是“代码旧”,而是规则没有被写出来。
什么地方能改,什么地方最好别碰; 平时怎么跑验证,做到什么程度算能交付; 目录是怎么分层的,哪些约束是明确存在的,哪些只是过去协作中慢慢形成的默契。
这些事情,人和人协作的时候还可以靠经验补齐。 但 AI 看不到这些隐性约定。它能看到的,只有代码表面和你显式给它的内容。
这也是为什么我觉得,老项目如果直接让 AI 上来就加功能,体验通常不会太好。

不是因为它一定做不出来,而是因为在约束缺失的情况下,它只能根据表面结构去猜。 一旦进入“猜”的阶段,结果就会忽高忽低,很难稳定。
所以在这个场景里,我没有让 AI 一开始就做功能。 我先让它做的是“补地基”。
## 目标
针对一个“可运行但约束不完整”的前端项目,先补齐最小工程规范,再为后续功能迭代建立稳定基础。
## 本次要做的事
在不大规模重构的前提下,优先补齐以下内容:
1. README
2. 统一 scripts
3. lint / test / build 基线
4. 最小测试样例
5. 项目结构说明
6. 必要的工程说明
## 本次任务原则
– 只补最小可执行规范
– 不进行大范围重构
– 不借机重写项目
– 不调整无关业务逻辑
– 以“先建立最小秩序”为目标
## 结果
要求本次完成后,项目至少应满足:
– README 可读
– 常用命令统一
– lint 可执行
– test 可执行
– build 可执行
– 至少存在 1 条最小测试样例
这里说的补地基,不是大规模重构,也不是借机把历史项目重新整理一遍。那样事情会立刻变大,也不符合这次验证的目标。我更关注的是另一件事:能不能先把最低限度的秩序补起来。
这次我故意把项目退化到了一个很常见的状态:README 只剩下标题和一句非常泛的描述,虽然项目本身还能跑,但一个新接手的人几乎拿不到任何可执行信息。不知道怎么启动,不知道怎么验证,也不知道目录结构该怎么理解。
而在真正开始改动之前,我先让 Claude Code 做了一次问题盘点。它先总结当前项目的主要问题,再区分这次应该优先解决什么、明确不解决什么,最后给出最小变更策略。
这个顺序很重要,因为到了老项目场景里,最容易失控的地方,就是一上来就借题发挥,把“补规范”做成“顺手重构”。
这次它最终收敛下来的改动其实非常少,只动了 3 个文件:
- README.md
- package.json
- husky/pre-commit

其中最核心的变化,是把 README 从一个几乎没有信息的状态,补成了一份最小可执行说明。至少把下面这些内容补回来了:
- 项目简介
- 常用命令
- 项目结构说明
- 开发说明

这一步看起来不“炫”,但价值非常大。因为从这一刻开始,这个项目不再只是“能跑”,而是开始变成一个别人也能接手、AI 也能稳定理解的项目。
除了 README,这次还顺手补了一层很轻但很有用的门禁:把 pre-commit 从只跑 pnpm test,补成了同时执行 pnpm lint 和 pnpm test。这不是在做复杂治理,而是在补最基础的提交前约束,让最小工程基线真正形成闭环。
如果把这次变化抽象成一张表,大概就是这样:

更重要的是,这次我还刻意控制住了“顺手多做一点”的冲动。 比如盘点里其实还发现了 eslint 残留依赖这类可以继续清理的问题,但我最后没有继续扩下去。
原因很简单:这一轮的目标不是把项目彻底整理干净,而是先把最低限度的秩序补起来。如果这个边界守不住,场景三就会从“补约束”滑向“借机治理一切”。
完成后,我还是用最直接的方式做了一次验证:
$ pnpm lint
Checked 11 files in 3ms. No fixes applied.
$ pnpm test
Test Files 2 passed (2)
Tests 6 passed (6)
$ pnpm build
✓ built in 401ms
全部通过。
到这里,我对场景三的判断就很明确了:老项目并不是不能让 AI 参与。但如果约束长期缺失,直接做功能的体验通常会比较差。
更合理的顺序,往往是先补最小规范,再逐步把后面的功能迭代交给它。换句话说,在老项目里,最先要补的,不是功能,而是约束。
六、AI Coding 方法论
——做完这三个场景之后,我真正沉淀下来的东西
把这三个场景都跑完之后,我回头再看,会觉得真正值得留下来的,并不是某一个页面或者某一段实现。
更有价值的,是我慢慢形成了一些更稳定的做法。
这些做法看起来都不算复杂,甚至很多都不新鲜。但它们一旦固定下来,确实会明显影响整个过程的稳定性。
也就是从这个意义上说,这次实践留下来的,不只是代码,而是几套后面还可以继续复用的工作方式。
我最后沉淀下来的几条固定做法,大致是这几项:
- 先把需求写清楚,再让 AI 动手
- 先看计划,再看实现
- 小步改动,及时验证
- 新能力尽量保留回退空间
- 老项目先补规范,再做功能
- 对界面功能尽量补一层接近真实使用的自动化验收

1. 先把需求写清楚,再让 AI 动手
这听起来像一句正确的废话,但真正做起来,其实很容易偷懒。尤其是当你已经大概知道自己想要什么时,会下意识觉得有些前提不用写,边做边补就行。
但这次实践里,我越来越确定,如果需求本身还是散的,后面所有结果都会跟着一起变得不稳定。
所以我现在会更愿意先花一点时间,把目标、边界、规则和验收写清楚。 这一步看起来慢,实际上是在减少后面来回修正、反复返工的成本。
2. 先看计划,再看实现。
现在我基本不会一上来就盯着代码。 我更先看它准备怎么做,会影响哪些文件,边界在哪里,依赖会不会加多,验证方式是否合理。
因为一旦方向偏了,后面代码写得再认真,也只是沿着一个不够理想的方向越走越远。 很多时候,真正省时间的,不是更快动手,而是更早发现方向问题。
3. 尽量让改动保持在较小范围内,并且尽快验证。
无论是新项目还是已有项目,我都会越来越在意“这一步是不是太大了”。 任务一旦被拆小,很多问题都会更早暴露,也更容易收住。
相反,如果一次性交给 AI 一个过长的任务链,表面上看省了中间步骤,实际上往往只是把问题延后了。
4. 尽量给新能力保留回退空间。
这次实践里,我对这一点的感受也很明显。尤其在 AI 参与度比较高的情况下,能不能快速回退,其实会直接影响你用它时的安全感。
像 feature flag、fallback 这种做法,不只是上线手段,本质上也是一种开发过程中的风险缓冲。它让你在推进的时候,有更多收手和调整的空间。
5. 遇到老项目时,先看约束是不是存在,再决定要不要继续做功能。
以前很多时候,看到一个项目结构不太清楚,也会默认“先把需求做出来再说”。
但这次实践之后,我会更倾向于先停一下,看看这个项目是不是连最小规范都没有。
如果没有,那就先把这些基础设施补起来。因为很多所谓的“AI 不稳定”,最后追根结底并不是模型的问题,而是输入环境本身就很模糊。
6. 对界面功能尽量补一层接近真实使用的自动化验收。
单测、lint 和 build 当然重要,但它们更多还是工程内部视角。
至少对表单、列表这类前端功能,我现在会更倾向于再补一层从用户视角出发的自动化验收。这样验证结果会更接近真实交付,而不只是停留在代码层面。
七、结语
这次实践做完之后,我对 AI Coding 的理解,和一开始相比,确实有了一些变化。
以前更容易把它看成一个生成代码的工具。 你给它一个任务,它返回一段实现;你给它一段报错,它再继续帮你修。整个过程更多是在围绕“代码本身”打转。
但现在我会更倾向于把它看成一个需要被约束、被验证、被管理的协作对象。
这并不是说它变得更复杂了,而是因为当你开始希望它完整参与交付时,关注点自然就会从“它会不会写”转移到“它能不能稳定地按要求把事情做完”。
从这个角度看,我现在其实没那么在意“它到底有多聪明”。 我更在意的是,在边界清楚、约束明确、验收可执行的前提下,它能不能把一段任务稳定地接住。
因为对真实开发来说,可预期往往比偶尔惊艳更重要。
至少在我这次实践覆盖的几个场景里,AI 已经不只是一个局部编码工具了。 在明确约束、明确边界、明确验收的前提下,它确实已经可以承担相当一部分完整实现工作。
但这个前提始终都在: 问题得先被定义清楚。
如果问题本身还是散的,很多约束都停留在默认状态,需求也只是一个方向性的描述,那 AI 参与进来,大概率只是把原本已经模糊的东西更快地放大而已。
所以如果让我用一句话来总结这次实践,我大概会这样说:
AI 能不能真正参与交付,最后比拼的,往往不是生成速度,而是问题定义的质量。
至少对我来说,这次实践最大的收获,不是少写了多少代码,而是我开始更认真地对待“把事情说明白”这件事。
本文由人人都是产品经理作者【叶小钗】,微信公众号:【叶小钗】,原创/授权 发布于人人都是产品经理,未经许可,禁止转载。
题图来自Unsplash,基于 CC0 协议。
- 目前还没评论,等你发挥!

起点课堂会员权益



