扫码打开虎嗅APP

搜索历史
删除
完成
全部删除
热搜词
AI Coding已能参与完整交付,但前提苛刻。其价值不再局限于代码生成,而是成为需要被明确定义、约束和验证的协作对象。真正决定其交付稳定性的,是问题定义的质量而非模型的生成能力。 ## 1 输入>模型:决定结果的关键是任务定义 - 真正影响AI Coding结果的,往往不是模型本身,而是输入的完整性和清晰度。当任务描述模糊时,AI只能靠“猜”,导致结果不稳定。 - 从实践来看,将任务定义从方向性描述升级为一份包含目标、边界、规则和验收方式的“执行说明”,能显著提升AI输出的稳定性。 ## 2 配合模型:需求文档需为AI而写 - 传统需求文档是为人类协作设计的,依赖大量默认前提和隐性知识,但这在AI面前行不通。AI只能依赖被明确写出的信息。 - 若要让AI真正参与交付,需求文档必须像一份可直接执行的说明,具体到“做什么”、“不做什么”、“如何验证”,将过去“默认大家都知道”的内容显式化。 ## 3 从0到1:新项目需先立工程约束 - 在从零搭建新项目时,关键不是快速生成页面,而是先建立清晰的项目基线,包括目录结构、统一脚本、验证工具链和可读文档。 - 实践表明,在明确的技术栈和工程规格下,AI不仅能初始化项目,还能完成带校验、Feature Flag和测试的最小功能,并通过自动化验收,实现从初始化到验收的完整闭环。 ## 4 难点:在已有项目中可控地迭代 - 在已有项目中加需求,真正的难点并非功能实现,而是控制改动边界,避免“顺手优化”导致结构变形或范围失控。 - 有效做法是要求AI在动手前先输出“文件改动清单”和“实现计划”,明确影响范围和风险点。在边界清晰的前提下,AI能稳定完成小到中等规模的迭代。 ## 5 老项目:先补规范,再做功能 - 对于约束缺失、文档不全的老项目,直接让AI开发功能体验差且不稳定,因为其缺乏可遵循的显性规则。 - 更合理的路径是优先进行“最小化补地基”,即在不进行大规模重构的前提下,补齐README、统一脚本、建立Lint/Test/Build基线,先建立最小秩序,再迭代功能。 ## 6 沉淀的AI Coding方法论 - **先定义后执行**:花时间写清需求的目标、边界和验收标准,是减少返工的最高效方式。 - **先看计划再看代码**:审查AI的实现计划,及早发现方向偏差,比事后修正代码更省时。 - **小步快跑,及时验证**:将任务拆小,尽快验证,避免问题延后放大。 - **保留回退空间**:为新功能引入Feature Flag等机制,作为开发过程中的风险缓冲。 - **老项目先补约束**:面对老项目,优先评估并补齐最小工程规范,再开展功能开发。 - **补全用户视角验收**:除单元测试外,为界面功能补充如Playwright的自动化验收,确保从用户视角验证功能。
2026-03-11 08:38

实战报告:AI Coding 已经能做交付了,但前提苛刻

本文来自微信公众号: 叶小钗 ,作者:叶小钗,原文标题:《【万字】实战报告:AI Coding 已经能做交付了,但前提苛刻》


首先,为什么我想认真做这次AI Coding验证?


现在大家对AI Coding已经不陌生了,代码补全、写工具函数、起一个页面、做一个Demo,这些事情它大多都已经能做,而且很多时候做得还不错。


所以如果只是讨论“AI会不会写代码”,这个问题其实已经没有太多讨论价值了。至少在局部编码这件事上,它的可用性基本已经被反复验证过了。


但我一直更想确认的是另一件事:


如果把问题往前和往后都拉长一点,不再只是让它补某一段代码,而是让它真正进入一段完整的交付过程。


从理解需求开始,到实现功能,再到验证和收口,那它现在到底能做到什么程度?


换句话说,我这次真正想验证的,不是AI能不能辅助写代码,而是它有没有可能从局部参与走到完整参与交付。


这个问题,看起来只差一点,实际差得很远。


局部参与,验证的是它的编码能力;完整参与交付,验证的其实是它在一整段任务里的稳定性:AI Coding能不能理解边界,能不能按要求推进,能不能在约束下把事情做完,或者只是产出几段看起来还行的代码。



为了把这个问题看得更清楚一点,我没有直接拿现有业务项目来试。


一方面,业务项目本身不适合公开展示;另一方面,我也不希望把业务复杂度、历史包袱和真实约束全部搅在一起,最后很难判断,到底是AI的能力到了边界,还是测试条件本身不够干净。


所以这次我专门搭了一个脱敏的演示项目,并刻意把验证拆成了三个场景:


  1. 第一个场景,是从0到1,直接让AI按要求搭一个新项目,并完成一个最小功能;


  2. 第二个场景,是在已经搭起来的项目里继续加需求,模拟更接近日常开发的迭代过程;


  3. 第三个场景,则是模拟一个文档不全、约束不清的老项目,先补规范,再继续做功能;


这三个场景连起来,基本就是我想验证的那条完整问题链:AI今天到底能不能真正参与一段完整交付,而不只是把局部编码做得更快?



如果先给结论,我现在的判断是:


AI Coding已经不只是适合写Demo、补函数、起页面了。


在约束清楚、边界明确、验收方式可执行的前提下,它已经可以参与相当一部分真实交付工作


但这个结论不是无条件成立的。从这次实践看,它更适合三类项目场景:


  1. 新项目:适合先搭基线,再落最小功能


  2. 结构相对清楚的已有项目:适合在边界明确的前提下继续迭代


  3. 约束缺失但仍可运行的老项目:适合先补最小规范,再继续往下做


如果换一种更直接的说法,我现在会把AI编程的可用范围理解成这样:


  1. 做Demo、局部编码、小功能实现:已经比较成熟


  2. 做新项目初始化和最小功能闭环:可以承担


  3. 做已有项目中的小到中等规模迭代:可以承担,但前提是边界要先框清楚


  4. 做老项目直接功能开发:不建议直接开始,更适合先补规范、再做功能


  5. 做高不确定性、强业务耦合、约束大量依赖隐性经验的任务:仍然不够稳


也就是说,它已经可以参与交付,但更像一个需要被定义清楚、被约束好、被验证到位的协作对象,而不是一个把需求丢过去就能自动收口的全能开发者。


下面这三个场景,就是我这次为了验证这个判断,专门拆出来的一条完整实验链路。


unsetunset输入>模型unsetunset


——真正影响结果的,往往不是模型,而是输入


刚开始做这次实践的时候,我的想法其实也比较直接:既然是想验证AI能不能承担更多工作,那最自然的方式,就是把任务交给它,让它尽量往下做,我再回来检查、修正和收尾。



说得简单一点,就是先看看它到底能接住多少。这种用法在很多小任务上其实没有问题,甚至会让人觉得很顺。


尤其是那些上下文比较简单、边界也比较清楚的事情,比如补一个函数、写一个局部页面、改一个相对独立的小功能,AI往往能很快给出一个还不错的结果。


也正因为如此,最开始的时候,很容易形成一种判断:好像只要需求描述得差不多,后面的事情就可以交给它了。


但等任务稍微完整一点,问题就开始慢慢冒出来:


  1. 有时候是功能虽然做出来了,但一些关键细节和预期并不一致;


  2. 有时候是代码能跑,但实现方式和项目原本的组织方式并不统一;


  3. 还有一些情况是,你以为理所当然的前提,其实根本没有写出来,于是AI会自己去补全,而它补出来的东西未必就是你真正想要的;


后来我慢慢意识到,这里面最麻烦的地方,往往并不是“它把代码写错了”,而是很多偏差其实在写代码之前就已经出现了。更准确地说,很多问题发生在“理解任务”这一步。


当任务只是局部编码时,这个问题还没那么明显。因为上下文很短,目标也比较集中,AI就算理解得不够完整,影响范围通常也有限。


但一旦任务开始变长,开始涉及需求边界、功能约束、验证标准、回退方式这些东西,输入本身如果不完整,后面就很难稳定。


也就是从这个时候开始,我越来越确定:决定结果的关键,不只是模型,而是我们到底给了它什么样的输入。


如果输入只是一个方向性的描述,那AI很多时候做的,其实不是执行,而是在猜。它一边写代码,一边补我没有明确说出来的前提。


而问题恰恰在这里:那些没被写出来的前提,很多时候才是真正决定结果稳不稳的东西。


所以后来我调整的重点,不再是“怎么把提示词写得更花”,而是另一件更基础的事情:怎么把任务定义得更完整一点。


我开始不满足于只给它一个需求描述,而是尽量把目标、边界、规则、验收方式这些内容提前写清楚,让它面对的不是一个模糊任务,而是一份相对明确的执行说明。


我后来会把模糊描述和结构化任务定义放在一起看,差异会非常直观:



左边这种描述,人能靠经验补全很多前提;但放到AI面前,往往就会变成大量默认假设。


右边这种写法看起来更“啰嗦”,但真正能明显提升稳定性的,通常恰恰是这些被显式写出来的边界、规则和验收条件。


做完这次实践之后,我越来越确定,真正影响结果的,往往不是模型本身,而是你给它的输入,到底是不是一份足够清楚的任务定义。


unsetunset去配合模型unsetunset


——如果想让AI参与交付,需求就不能只写给人看


以前写需求,更多是为了让人看懂。业务目标说清楚,流程大致讲明白,关键页面和规则列出来,通常也就可以往下推进了。


很多默认前提不一定会专门写出来,因为团队里的人大多知道背景,也知道哪些地方该怎么处理。即便文档里没写得特别细,很多信息也能在协作过程中慢慢补齐。


但这次实践让我越来越明确地感受到,AI面对的不是这样的协作环境。


人和人协作时,很多东西是可以靠经验、上下文和来回沟通补上的。但AI不一样。它能依赖的,基本就是你明确给到它的那部分信息


这就意味着,以前那些“默认大家都知道”的内容,放到AI面前,其实很多都应该重新写出来。否则它只能根据现有输入去猜,而一旦进入猜的阶段,结果就会变得不稳定。


所以后来我开始要求自己,把需求写得更具体一些:


  1. 不只是写“这次要做什么”,还要写“哪些事情这次不做”;


  2. 不只是写功能本身,还要写规则和边界;


  3. 不只是写页面和交互,还要写最后怎么验证算完成;


  4. 如果有上线风险,还要提前考虑有没有回退空间;


说得更直接一点,我后来更希望需求文档像一份能直接执行的说明,而不只是一个方向性的描述。


比如这次实践里,其中一个很小的功能,是新增一个用户表单。


如果只是写一句“做一个新增用户表单页面”,那这个任务其实非常松。字段怎么设计、校验做到什么程度、有没有开关控制、要不要补测试、做到什么算完成,这些全都没有边界,AI只能自己去补。


但如果把这些前提往前补一点,事情就会清楚很多:


##目标


在当前项目中实现一个“新增用户表单”功能,并通过feature flag控制新表单是否启用。


##校验规则


###name


-必填


-长度2到20


###email


-必填


-必须符合邮箱格式


###role


-必填


-枚举值固定为:


-`admin`


-`editor`


-`viewer`


##Feature Flag要求


新增`newUserForm`开关,并满足:


-默认关闭


-开启时展示新表单


-关闭时展示占位内容或旧版占位区域


feature flag要显式、易定位。


##测试要求


至少补充以下测试:


1.feature flag开关对应的渲染分支


2.表单校验错误展示


如果实现方式合理,可补更多,但不要为了数量堆测试。


##验收标准


完成后至少满足:


1.新表单可渲染


2.校验规则生效


3.feature flag生效


4.`pnpm lint`通过


5.`pnpmtest`通过


6.`pnpm build`通过


PS:其实从这里大家就可以直观感受到了,代码不再是必须,自然语言就是代码


这也是我这次实践里最深的一个感受之一:


如果目标只是让AI帮忙写点代码,那以前那种偏描述性的需求文档也许还能凑合;但如果目标是让它真正参与交付,需求本身就不能只写给人看。


需求一旦写清楚,AI不一定会突然变得更“聪明”,但它的表现通常会明显稳定很多。


unsetunset从0到1unsetunset


——拿一个新项目做验证


第一个场景,我故意从一个最“空”的状态开始。


原因很简单。如果AI连从0到1都接不住,后面讨论它怎么参与已有项目的迭代,其实意义并不大。因为那种情况下,它更多还是在已有上下文里“顺着写”,而不是在真正承担一段完整的起步工作。


而且从0到1还有一个好处:很多问题会暴露得更直接。


没有现成目录,没有既有约定,也没有默认上下文。这种情况下,如果要把一个项目真正搭起来,AI面对的就不只是“写代码”本身,而是要先处理一层更基础的东西:项目结构怎么组织、脚本怎么约定、验证方式怎么建立、后面还能不能继续迭代。


所以这个场景里,我一开始并没有把目标设成“让它快速生成一个页面”,而是先给自己定了一个更明确的判断标准:这个项目至少要像一个能继续发展的项目,而不只是一个跑起来的壳子。


也就是说,它要满足的不只是“能启动”,还包括:


  1. 结构要足够清楚


  2. 脚本要统一


  3. 基本验证要能跑通


  4. 后面还能继续往里加需求


  5. 整个项目要适合拿来做后续两个场景的继续验证


从这个角度看,我要验证的其实不是“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`


-`pnpmtest`


-`pnpm build`


-`pnpm dev`


##README要求


README至少说明:


-项目如何安装依赖


-项目如何启动


-如何执行lint/test/build


-基础目录结构说明


##验收标准


完成后至少满足:


1.`pnpm lint`可执行


2.`pnpmtest`可执行


3.`pnpm build`可执行


4.README可读


5.目录结构清晰


这份规格里没有太多复杂内容,主要就是把一些项目级别的约束先说清楚,比如技术栈、目录结构、脚本要求、测试基线,以及README至少需要说明哪些东西。


换句话说,我希望AI面对的不是一句“帮我起个项目”,而是一份更明确的初始化要求。


这个动作看起来有点“慢”,但后来回头看,它其实非常关键。


因为从0到1的场景里,最容易出的问题往往不是代码写错,而是方向一开始就偏了。


比如依赖引得太多、目录切得太散、脚本不统一、测试环境缺失,或者为了图快临时拼了一套不太能继续维护的结构。短期看它们都不是大问题,但如果一开始没有收住,后面每加一个需求,都会不断放大这些问题。


所以在真正让它动手之前,我没有直接要求它开始改代码,而是先让它把计划说出来:



我会先看:


  1. 它准备怎么拆步骤


  2. 它觉得要新增或修改哪些文件


  3. 依赖会不会加得过多


  4. 它准备怎么验证结果


这个阶段,我关注的重点不是“它写得快不快”,而是“它理解得对不对”。


因为很多偏差,其实在真正写代码之前就已经出现了。如果方向一开始偏了,后面代码写得再认真,也只是沿着一个不够理想的方向越走越远。


等这些都看起来没问题了,后面的实现反而是顺下来的。到了这个阶段,我真正关心的,也不再是它一共写了多少代码,而是最后这些东西能不能顺利通过验证。


$pnpm lint


...


Checked 6 filesin2ms.No fixes applied.


$pnpmtest


...


Test Files 1 passed(1)


Tests 2 passed(2)


...


$pnpm build


...


✓builtin397ms


这个场景做到这里,其实只能证明一半:AI已经能把项目基线搭起来,并把最基本的工程约束先立住。


但如果只停在这里,还不能完全说明它已经具备继续交付功能的能力。


所以项目基线搭起来之后,我没有马上进入第二个场景,而是先在这个新项目里落了一个最小功能:新增用户表单。


这一步很重要,因为如果只做到bootstrap,其实还只能说明AI能把项目框架搭起来,还不能完全说明它已经具备继续交付功能的能力。


只有当它能在这个基线上继续完成一个具体功能,并把校验、开关、测试这些细节一起接住时,场景一才算真正闭环。


所以我给它的第一个功能,不是很复杂,但故意保留了几个很“像真实项目”的要素:


  1. 表单字段与校验规则


  2. feature flag


  3. 成功态提示


  4. 最低限度的测试要求


  5. 明确的验收命令


##功能要求


新增一个用户表单,包含以下字段:


-`name`


-`email`


-`role`


##校验规则


###name


-必填


-长度2到20


###email


-必填


-必须符合邮箱格式


###role


-必填


-枚举值固定为:


-`admin`


-`editor`


-`viewer`


##交互要求


-展示字段标签


-展示校验错误信息


-提供提交按钮


-提交可使用mock逻辑,不接后端


-提交成功后有明确成功提示


##Feature Flag要求


新增`newUserForm`开关,并满足:


-默认关闭


-开启时展示新表单


-关闭时展示占位内容或旧版占位区域


feature flag要显式、易定位。


##测试要求


至少补充以下测试:


1.feature flag开关对应的渲染分支


2.表单校验错误展示


如果实现方式合理,可补更多,但不要为了数量堆测试。


##验收标准


完成后至少满足:


1.新表单可渲染


2.校验规则生效


3.feature flag生效


4.`pnpm lint`通过


5.`pnpmtest`通过


6.`pnpm build`通过


从实际改动文件看,这次实现也基本控制在了一个很小的范围内:页面层负责接住功能入口,组件层负责表单本身,lib层负责规则和开关,测试则分别覆盖页面分支和表单行为。



其中一个我刻意要求保留的点,是feature flag。因为我不想验证的只是“页面能不能做出来”,而是“这个能力是否具备最小回退空间”。


//featureFlags.ts


exportconst featureFlags={


newUserForm:false,


}


校验逻辑我也要求尽量集中,而不是散落在组件内部。这样一方面更贴近真实项目的写法,另一方面也更容易测试和复查。


//validation.ts核心代码


exportconst roleOptions=['admin','editor','viewer']as const


exportfunctionvalidateUserFormValues(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的范围控制得很小,只覆盖关键路径:


  1. 空表单提交时是否能展示必填校验错误


  2. 非法输入时是否能展示对应错误


  3. 合法输入后是否能出现成功提示


这一步做完后,我对场景一的判断才真正完整起来:


从0到1,AI不只是能把项目基线搭起来,也不只是能把第一个功能写出来,而是已经能够借助自动化工具,把这个功能从实现一路推进到最基本的验收。


如果一开始只是追求“先跑起来”,那很容易得到一个表面很快、后面却越来越难接着做的项目。反过来,如果先把项目级别的规则说清楚,再让AI往下执行,并在最后补上一层真正从用户视角出发的自动化验收,整个过程会稳很多。


unsetunset难点:加需求unsetunset


——项目搭起来之后,真正难的是继续往里加需求


第一个场景做完之后,项目至少已经站住了。基线有了,最小功能也有了,单测、构建和一轮自动化验收也都跑通了。


做到这里,已经足以说明AI不只是会“起一个项目”,也不只是会“写一个页面”,而是已经能在明确约束下,把一段从初始化到最小验收的链路接起来。


但这还不够。因为真实开发里,大多数时候并不是从0开始,而是在一个已经存在的项目里不断往下加需求、补功能、做调整。


这也是第二个场景更接近日常开发的地方。


项目已经有了基础结构,页面也已经起来了,测试和构建也都在。这个时候继续往里加功能,表面上看比从0到1更简单,但实际上,真正麻烦的地方反而开始出现了。


因为在已有项目里,问题的重点往往不再是“能不能写出来”,而是“会不会把原来的东西带乱”。


这类场景里最容易出现的问题,大概有三种。


  1. 改动范围失控。本来只是一个不大的需求,但做着做着会牵出越来越多修改;


  2. 顺手碰了不该碰的地方,为了实现这次需求,把原有结构也一起改了;


  3. 功能虽然加进去了,但实现方式和项目原本的组织方式并不一致,表面上完成了,实际上留下了新的不协调。


所以到了这个阶段,我自己的使用方式会有一个明显变化。


在场景一里,我更关心的是“它能不能先把项目立起来”;但到了场景二,我先问的就不再是“怎么实现”,而是“会改哪些地方”:


##目标


在现有项目基础上新增一个用户列表页面,用于模拟已有项目中的功能迭代。


##功能要求


1.展示mock用户列表


2.提供搜索输入框


3.支持按`name`或`email`过滤


4.无结果时展示空态


##本次任务重点


这个任务的重点不只是实现列表,而是控制改动边界。


开始实现前,需要先明确:


-会新增哪些文件


-会修改哪些文件


-改动边界在哪里


-风险点是什么


不要顺手调整无关结构。


##输出要求


开始改动前,先输出:


1.文件改动清单


2.实现计划


3.风险与边界说明


4.验证命令


也就是说,我不会让它一上来就直接给我代码,而是会先要求它把边界说清楚:


  1. 这次会影响哪些页面或模块


  2. 哪些文件需要新增,哪些文件需要修改


  3. 改动范围大概有多大


  4. 风险点主要在哪里


  5. 最后准备怎么验证


这个动作非常重要。因为在已有项目里,很多问题不是功能本身做错了,而是本来一个很小的需求,最后却牵扯出一片不必要的改动。范围一旦失控,后面的验证成本就会明显上升,整个过程也更难收口。


换句话说,在已有项目里,我更关心它打算动哪里,而不是它打算怎么写。


这背后其实是一个很现实的考虑:如果边界先清楚了,后面的过程通常都会稳很多。你知道它准备碰什么地方,也知道哪些地方最好不要动,那么后面的验证、回看和继续迭代,都会容易很多。


反过来,如果一开始没有把边界框出来,AI很容易凭它自己的理解去“顺手优化”一些东西。单看每一处修改,可能都不算离谱,但整体上很容易让结构慢慢变形。尤其是在一个已经有既有做法的项目里,这种“顺手改一点”的累计代价会非常高。


这次我在已有项目上新增的是一个用户列表页,功能本身并不复杂,主要包括:


  1. 展示mock用户数据


  2. 支持按姓名或邮箱搜索


  3. 搜索无结果时展示空态


这个功能刻意选得比较克制。因为我的目标不是用一个复杂需求去证明AI有多强,而是想验证:在一个已经成型的项目里,它能不能在边界明确的前提下,继续把事情做稳。


从最终落地结果看,这次改动基本也保持在了一个比较小的范围内:


App.tsx只做了最小的页面切换入口,用useState在“创建用户”和“用户列表”两个页面之间切换;


UserListPage.tsx作为独立页面承接列表、搜索框和空态展示;


mockData.ts和userFilter.ts则把数据和过滤逻辑放回到了lib层,没有直接揉进页面组件里。



这一步让我比较满意的一点,不是它把列表做出来了,而是它没有为了一个小需求去撬动原有结构。


它没有顺手引入路由库,也没有借机扩展分页、排序、编辑删除这些spec里根本没要求的内容。整个改动看起来更像一次正常的功能迭代,而不是一次失控的“顺便升级”。


从页面代码本身看,这种边界感也比较明显:App.tsx只负责页面切换和渲染,不承载列表业务逻辑。


例如这次实际就是用一个很轻的本地状态做页面切换:


//App.tsx


typePage='create'|'list'


const[currentPage,setCurrentPage]=useState('create')


{currentPage==='create'?:}


而列表数据和过滤规则也没有直接写进页面里,而是都放在了lib层。数据本身只是一个很轻的mock集合,但结构已经是清楚的:


exportinterface User{


id:string


name:string


email:string


role:string


}


exportconst 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


exportfunctionfilterUsers(users:User[],query:string):User[]{


if(!query.trim()){


returnusers


}


const lowerQuery=query.toLowerCase()


returnusers.filter(


(user)=>


user.name.toLowerCase().includes(lowerQuery)||


user.email.toLowerCase().includes(lowerQuery),


)


}


这一点很关键。因为它意味着这次迭代不是简单把功能“糊上去”,而是尽量沿着已有结构,把页面职责、数据职责和规则职责分开。这种分层看起来并不复杂,但它会直接影响后面这个项目还能不能继续往下演化。


测试这一步,我也仍然保持了和前面一样的思路:不追求数量,而是优先覆盖关键行为。


这一轮最重要的测试点其实很直接:


  1. mock用户列表是否能正常渲染


  2. 按姓名搜索时过滤逻辑是否生效


  3. 按邮箱搜索时过滤逻辑是否生效


  4. 当没有匹配结果时,空态是否会正确出现


//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,关键是先立约束;而在已有项目里继续往下走,关键则是先控边界。


unsetunset最后问题:老项目unsetunset


——老项目最先要补的,不是功能,而是约束


如果只做前两个场景,这次验证其实还不完整。


因为新项目也好,相对干净的存量项目也好,本身都还带着一种“理想环境”的前提:结构至少是清楚的,约束至少是写得出来的,验证方式至少能补得上。


但真实世界里,很多项目其实并不是这样。


真正更常见的情况是:项目已经跑了很久,功能不少,代码也不一定不能用,但很多最基础的东西其实是缺的。文档不全,脚本不统一,测试没有形成基线,目录结构也未必一致,很多规则都存在于人的脑子里,而不是写在项目里。


所以第三个场景我一定要单独测。因为对很多团队来说,这才是更接近现实的问题。


而且这种项目真正麻烦的地方,往往并不是“代码旧”,而是规则没有被写出来。


什么地方能改,什么地方最好别碰;平时怎么跑验证,做到什么程度算能交付;目录是怎么分层的,哪些约束是明确存在的,哪些只是过去协作中慢慢形成的默契。


这些事情,人和人协作的时候还可以靠经验补齐。但AI看不到这些隐性约定。它能看到的,只有代码表面和你显式给它的内容。


这也是为什么我觉得,老项目如果直接让AI上来就加功能,体验通常不会太好。



不是因为它一定做不出来,而是因为在约束缺失的情况下,它只能根据表面结构去猜。一旦进入“猜”的阶段,结果就会忽高忽低,很难稳定。


所以在这个场景里,我没有让AI一开始就做功能。我先让它做的是“补地基”。


##目标


针对一个“可运行但约束不完整”的前端项目,先补齐最小工程规范,再为后续功能迭代建立稳定基础。


##本次要做的事


在不大规模重构的前提下,优先补齐以下内容:


1.README


2.统一scripts


3.lint/test/build基线


4.最小测试样例


5.项目结构说明


6.必要的工程说明


##本次任务原则


-只补最小可执行规范


-不进行大范围重构


-不借机重写项目


-不调整无关业务逻辑


-以“先建立最小秩序”为目标


##结果要求


本次完成后,项目至少应满足:


-README可读


-常用命令统一


-lint可执行


-test可执行


-build可执行


-至少存在1条最小测试样例


这里说的补地基,不是大规模重构,也不是借机把历史项目重新整理一遍。那样事情会立刻变大,也不符合这次验证的目标。我更关注的是另一件事:能不能先把最低限度的秩序补起来。


这次我故意把项目退化到了一个很常见的状态:README只剩下标题和一句非常泛的描述,虽然项目本身还能跑,但一个新接手的人几乎拿不到任何可执行信息。不知道怎么启动,不知道怎么验证,也不知道目录结构该怎么理解。


而在真正开始改动之前,我先让Claude Code做了一次问题盘点。它先总结当前项目的主要问题,再区分这次应该优先解决什么、明确不解决什么,最后给出最小变更策略。


这个顺序很重要,因为到了老项目场景里,最容易失控的地方,就是一上来就借题发挥,把“补规范”做成“顺手重构”。


这次它最终收敛下来的改动其实非常少,只动了3个文件:

  1. README.md

  2. package.json

  3. .husky/pre-commit



其中最核心的变化,是把README从一个几乎没有信息的状态,补成了一份最小可执行说明。至少把下面这些内容补回来了:


  1. 项目简介

  2. 常用命令

  3. 项目结构说明

  4. 开发说明



这一步看起来不“炫”,但价值非常大。因为从这一刻开始,这个项目不再只是“能跑”,而是开始变成一个别人也能接手、AI也能稳定理解的项目。


除了README,这次还顺手补了一层很轻但很有用的门禁:把pre-commit从只跑pnpm test,补成了同时执行pnpm lint和pnpm test。这不是在做复杂治理,而是在补最基础的提交前约束,让最小工程基线真正形成闭环。


如果把这次变化抽象成一张表,大概就是这样:



更重要的是,这次我还刻意控制住了“顺手多做一点”的冲动。比如盘点里其实还发现了eslint残留依赖这类可以继续清理的问题,但我最后没有继续扩下去。


原因很简单:这一轮的目标不是把项目彻底整理干净,而是先把最低限度的秩序补起来。如果这个边界守不住,场景三就会从“补约束”滑向“借机治理一切”。


完成后,我还是用最直接的方式做了一次验证:


$pnpm lint


Checked 11 filesin3ms.No fixes applied.


$pnpmtest


Test Files 2 passed(2)


Tests6 passed(6)


$pnpm build


✓builtin401ms


全部通过。


到这里,我对场景三的判断就很明确了:老项目并不是不能让AI参与。但如果约束长期缺失,直接做功能的体验通常会比较差。


更合理的顺序,往往是先补最小规范,再逐步把后面的功能迭代交给它。换句话说,在老项目里,最先要补的,不是功能,而是约束。


unsetunsetAI Coding方法论unsetunset


——做完这三个场景之后,我真正沉淀下来的东西


把这三个场景都跑完之后,我回头再看,会觉得真正值得留下来的,并不是某一个页面或者某一段实现。


更有价值的,是我慢慢形成了一些更稳定的做法。


这些做法看起来都不算复杂,甚至很多都不新鲜。但它们一旦固定下来,确实会明显影响整个过程的稳定性。


也就是从这个意义上说,这次实践留下来的,不只是代码,而是几套后面还可以继续复用的工作方式。


我最后沉淀下来的几条固定做法,大致是这几项:


  1. 先把需求写清楚,再让AI动手


  2. 先看计划,再看实现


  3. 小步改动,及时验证

  4. 新能力尽量保留回退空间

  5. 老项目先补规范,再做功能

  6. 对界面功能尽量补一层接近真实使用的自动化验收


1.先把需求写清楚,再让AI动手


这听起来像一句正确的废话,但真正做起来,其实很容易偷懒。尤其是当你已经大概知道自己想要什么时,会下意识觉得有些前提不用写,边做边补就行。


但这次实践里,我越来越确定,如果需求本身还是散的,后面所有结果都会跟着一起变得不稳定。


所以我现在会更愿意先花一点时间,把目标、边界、规则和验收写清楚。这一步看起来慢,实际上是在减少后面来回修正、反复返工的成本。


2.先看计划,再看实现。


现在我基本不会一上来就盯着代码。我更先看它准备怎么做,会影响哪些文件,边界在哪里,依赖会不会加多,验证方式是否合理。


因为一旦方向偏了,后面代码写得再认真,也只是沿着一个不够理想的方向越走越远。很多时候,真正省时间的,不是更快动手,而是更早发现方向问题。


3.尽量让改动保持在较小范围内,并且尽快验证。


无论是新项目还是已有项目,我都会越来越在意“这一步是不是太大了”。任务一旦被拆小,很多问题都会更早暴露,也更容易收住。


相反,如果一次性交给AI一个过长的任务链,表面上看省了中间步骤,实际上往往只是把问题延后了。


4.尽量给新能力保留回退空间。


这次实践里,我对这一点的感受也很明显。尤其在AI参与度比较高的情况下,能不能快速回退,其实会直接影响你用它时的安全感。


像feature flag、fallback这种做法,不只是上线手段,本质上也是一种开发过程中的风险缓冲。它让你在推进的时候,有更多收手和调整的空间。


5.遇到老项目时,先看约束是不是存在,再决定要不要继续做功能。


以前很多时候,看到一个项目结构不太清楚,也会默认“先把需求做出来再说”。


但这次实践之后,我会更倾向于先停一下,看看这个项目是不是连最小规范都没有。


如果没有,那就先把这些基础设施补起来。因为很多所谓的“AI不稳定”,最后追根结底并不是模型的问题,而是输入环境本身就很模糊。


6.对界面功能尽量补一层接近真实使用的自动化验收。


单测、lint和build当然重要,但它们更多还是工程内部视角。


至少对表单、列表这类前端功能,我现在会更倾向于再补一层从用户视角出发的自动化验收。这样验证结果会更接近真实交付,而不只是停留在代码层面。


unsetunset结语unsetunset


这次实践做完之后,我对AI Coding的理解,和一开始相比,确实有了一些变化。


以前更容易把它看成一个生成代码的工具。你给它一个任务,它返回一段实现;你给它一段报错,它再继续帮你修。整个过程更多是在围绕“代码本身”打转。


但现在我会更倾向于把它看成一个需要被约束、被验证、被管理的协作对象。


这并不是说它变得更复杂了,而是因为当你开始希望它完整参与交付时,关注点自然就会从“它会不会写”转移到“它能不能稳定地按要求把事情做完”。


从这个角度看,我现在其实没那么在意“它到底有多聪明”。我更在意的是,在边界清楚、约束明确、验收可执行的前提下,它能不能把一段任务稳定地接住。


因为对真实开发来说,可预期往往比偶尔惊艳更重要。


至少在我这次实践覆盖的几个场景里,AI已经不只是一个局部编码工具了。在明确约束、明确边界、明确验收的前提下,它确实已经可以承担相当一部分完整实现工作。


但这个前提始终都在:问题得先被定义清楚。


如果问题本身还是散的,很多约束都停留在默认状态,需求也只是一个方向性的描述,那AI参与进来,大概率只是把原本已经模糊的东西更快地放大而已。


所以如果让我用一句话来总结这次实践,我大概会这样说:


AI能不能真正参与交付,最后比拼的,往往不是生成速度,而是问题定义的质量。


至少对我来说,这次实践最大的收获,不是少写了多少代码,而是我开始更认真地对待“把事情说明白”这件事。

本内容来源于网络 原文链接,观点仅代表作者本人,不代表虎嗅立场。
如涉及版权问题请联系 hezuo@huxiu.com,我们将及时核实并处理。

支持一下

赞赏

0人已赞赏

大 家 都 在 搜

好的内容,值得赞赏

您的赞赏金额会直接进入作者的虎嗅账号

    自定义
    支付: