ChatGPT、GitHub Copilot、Cursor 及其同类工具,现在甚至在你还没写完提示词之前,就能吐出一个 React 组件。给 Next.js 路由连接上 Supabase?几秒钟搞定。重构那个乱成一团的 TypeScript 工具函数?这里有三个选项,还附带了类型守卫 (type guards)。对于任何从事现代 Web 技术栈的人来说,这种体验简直近乎魔法。

我每天都在使用这些工具。我的技术栈是 Next.js、TypeScript 和 Supabase,AI 就坐在我的编辑器里,随时准备搭建自定义 Hook、生成数据库查询或清理混乱的条件逻辑。就局部而言,它的表现就像一个手速极快的初级开发人员。它对语法了如指掌。它记得那些我不得不去 Google 的 API 范围。它写样板代码写到累都不觉得烦。

但软件依然在不断崩溃。应用感觉越来越慢。客户仪表板出现延迟。边缘情况导致表单崩溃。如果 AI 让编程变得如此容易,为什么使用软件的感觉反而比几年前更糟了呢?

答案是:生成语法和构建软件并不是同一回事。

语法不等于架构

AI 处理 Token 的能力非常出色。让它写一个监听 Supabase 实时通道的 useEffect Hook,你会得到一段可以编译通过的代码。它能把一个没有类型的 JavaScript 文件转换成严格的 TypeScript,或者在你喝上第一口咖啡之前,就帮你搭建好一个带有 Zod 校验的表单组件。

它做不到的是理解你特定应用的轮廓。优秀的软件需要深思熟虑的状态管理、对竞态条件的谨慎处理,以及对数据存储位置与展示位置之间关系的清晰掌握。AI 看到的是眼前的单个文件,而不是整个系统。它把你的代码库看作一条平坦的文本走廊,而不是一个拥有承重墙的生命结构。

把它想象成一个从未真正住过房子的人设计的建筑师。他们能画出漂亮的平面图,知道卧室应该有多少扇窗户。但他们不知道水管在二月份容易在哪里漏水,也不知道哪条走廊在夏天的酷暑中会变得无法使用。这种生活经验才是让建筑屹立不倒的关键。代码也是如此。

两个摩擦点

当我让 AI 在没有严格护栏的情况下编写大段代码时,我发现同样的问题在反复出现。

首先,它会忽略你已经建立的设计模式。也许你的团队将所有的数据获取都提取到了专门的自定义 Hook 层;也许你对于 Supabase RLS 策略如何映射到前端辅助函数有一套严格的规范。AI 并不在乎这些。如果直接在按钮的 onClick 中写一个原始的 supabase.from().select() 能解决当前的提示词,它就会这么做。代码能跑,看起来甚至很整洁。但它是你代码库中的一个异类,而每一个异类都是未来重构的税收。六个月后,总得有人去寻找这根“针”,弄明白它为什么存在,然后小心翼翼地把它拉回正轨。

其次,在简单就能解决问题时,它却倾向于追求复杂性。这个工具是在规模庞大的代码库上训练出来的,那些代码库需要抽象工厂、复杂的 reducer 模式和多层高阶组件。当你让它构建一个简单的联系表单时,它很可能会甩给你一个状态机、一个 Context Provider 以及一个跨越三个文件的自定义 Hook 抽象。这个方案在技术上并没有错,只是太重了。每一层不必要的抽象都会增加认知债务。你并没有跳过工作,你只是带着利息推迟了它。

速度陷阱

这里存在一个危险的反馈循环。AI 让你的功能开发速度提升了一倍,但人类的注意力并不能以同样的速度扩展。如果你的交付时间缩短了一半,那么你在代码审查上花费的时间是增加了一倍吗?你写的测试是变多了,还是变少了?

在实践中,人们极容易信任生成的代码,因为它看起来非常权威:使用现代语法,注释恰到好处地分布在合适的位置,变量名听起来也很专业。而微妙的 Bug 就隐藏在这些光鲜的外表之下。比如 Hook 中的依赖数组漏掉了一个 setter;比如一个在技术上正确但允许了你忘记处理的 null 状态的 TypeScript 类型;比如一个在你的特定 Schema 中忘记考虑软删除行的 Supabase 查询。因为交付节奏的要求,你只是走马观花地扫一眼,而不是逐行阅读。周一的时候,这种速度让你感觉棒极了;而周五的调试会却要一直持续到半夜。

真正的代价

为此买单的并不是开发者,而是最终用户。

软件感觉越来越臃肿,因为复杂度的增长速度超过了团队的管理能力。我们正拿着让我们感到无所不能的工具,用更精简的团队构建着规模更大的应用程序。当一名开发者能在一下午的时间内搭建起整个仪表板时,组织就会期望在周三前看到三个仪表板。缺乏审慎的扩张会产生脆弱的系统。状态膨胀。包体积悄然增加。竞态条件成倍增加。界面看起来可能很现代,但用户点击返回按钮时它会重置,或者因为没人有时间去分析 AI 生成的数据获取瀑布流,导致注水 (hydration) 需要耗费四秒钟。

与机器协作,而非为机器工作

这并不意味着你应该把 AI 从编辑器中踢出去。它的意思是,你需要设定边界。

将它用于它擅长的领域。让它去写那些枯燥的内容:重复的 TypeScript 接口、模板化的 Supabase 查询、Jest 配置