最近在开发 SnowLead 时,我刻意做了一次接近“纯 Vibe Coding”的实验。平时自己写项目,通常会先判断需求规模、数据量、失败方式和后续维护成本,再决定要不要引入新的抽象和基础设施。这一次我反过来做:尽量站在一个非技术创业者或小企业老板的角度,只告诉 Claude Code 我要实现什么,让它自己阅读项目、设计方案、创建代码和继续完善。
结果很有意思。SnowLead 中一个原本非常简单的数据导入流程,我自己估计核心代码大约 500 行就能完成。Claude Code 最后却洋洋洒洒写到了接近 10,000 行,里面出现了线程池、任务调度、Retry、指数退避、并发控制、优雅关闭、状态管理,以及大量 Manager、Executor、Policy 和配置代码。
这些技术本身并没有错。真正的问题是:当前这个项目到底需不需要它们。Vibe Coding 的风险,不只是 AI 会不会写错代码。另一种更隐蔽的问题,是 AI 很容易把一个简单需求,扩展成一个远超当前业务规模的工程系统。
这次经历让我觉得,AI Coding 最大的风险之一,不一定是“代码写得差”,而是复杂度生成得太便宜。以前,开发者想增加线程池、任务调度、Retry、状态机和各种抽象,需要真的花时间去设计和实现。现在 Coding Agent 可以很快把这些东西全部生成出来,于是“未来可能需要”很容易变成继续加代码的理由。
SnowLead 的需求,本来非常直接
这次出问题的是 SnowLead 的数据源导入模块。SnowLead 未来需要接入不同来源的公司数据,而不同数据源的字段、格式和调用方式可能完全不同。为了避免每增加一个来源都重新修改大量 Java 业务代码,我希望把具体抓取和转换逻辑放进 Lua 脚本,让 Java 主要负责运行环境、数据库访问和基础流程控制。
核心流程其实非常简单:外部数据源 → Lua 抓取脚本 → 临时数据表 → Lua 转换脚本 → 正式业务表。抓取脚本负责从外部来源获取数据,并先保存原始结果。转换脚本再把不同来源的数据统一整理成 SnowLead 自己的业务结构,例如公司名称、地址、联系方式和其他字段,最后写进正式业务表。
第一阶段真正需要解决的问题也很有限:数据有没有抓回来?临时数据有没有保存?转换有没有成功?正式数据有没有写进去?如果某一步失败,能不能看到原因并重新执行?只要这些问题解决,第一版就已经具备实际价值。
关键提醒
第一版真正需要解决的,只是把数据稳定地从外部来源移动到正式业务结构中。流程短、职责明确,每一步都能单独观察和排查,才是早期产品最该追求的状态。

但 AI 很快开始解决“还没有发生的问题”
Claude Code 的思路并不是明显错误。它看到外部 API,就开始考虑请求时间和失败情况;看到任务执行,就开始考虑异步和并发;看到未来可能接入多个数据源,就开始考虑任务隔离、调度和扩展性。
于是线程池出现了。线程池出现以后,又自然需要考虑任务提交、队列、线程数量、取消、关闭和异常处理。外部请求可能失败,于是又出现 Retry 和指数退避;开始做 Retry 以后,又需要管理错误类型、重试次数和任务状态。再往后,任务执行、脚本运行、数据抓取、状态记录、异常处理和生命周期逐渐形成了自己的组件和抽象。
原本的流程是:抓取 → 临时保存 → 转换 → 正式保存。最后却逐渐变成:调度 → 任务管理 → Executor → 线程池 → Worker → Retry → 状态管理 → Lua Runner → 临时数据 → 转换管线 → 正式数据。每多一层,AI 都可以给出一个听起来合理的理由。
真正的问题不是这些理由有没有道理,而是:为什么这些问题一定要今天解决?
“以后可能需要”,是最容易让系统膨胀的一句话
软件开发里,“以后可能需要”非常常见。以后可能需要更高并发,以后可能会接更多数据源,以后可能要自动恢复任务,以后可能要水平扩展,以后可能要更复杂的调度。这些事情当然都有可能发生。
但如果一个项目按照未来所有可能出现的情况去设计,那么任何简单系统最后都可以无限复杂。对于成熟的大型平台来说,提前建设某些能力是合理的,因为业务规模、用户量和增长路径已经比较明确。但对于早期产品来说,情况往往完全不同。
今天的产品方向可能三个月后就改变,今天设计的扩展接口可能永远不会出现第二个实现,今天预留的高并发能力可能几年都用不上。这时候所谓“为未来设计”,很可能只是把今天的维护成本提前拉高。
未来可能需要,不等于现在值得实现。真正重要的工程判断,不是能不能想到更多边界情况,而是能不能判断哪些问题值得今天解决,哪些应该等真实压力出现以后再处理。
最危险的地方是:这些代码往往看起来都很专业
如果 AI 写出一个明显 Bug,反而比较容易发现。测试失败、接口报错、数据库写入异常、页面打不开,这些问题至少会给出明确反馈。即使不会写代码,也能知道系统出了问题。
过度工程化却完全不同。它可能运行得非常正常,甚至运行得很稳定。你打开线程池配置,没有明显问题;看 Retry 逻辑,也符合常见工程实践;任务状态、异常处理和优雅关闭看起来也都很完整。于是很容易出现一个很奇怪的情况:每一部分单独看都没有明显错误,但整个系统已经走偏了。
因为真正的问题不是“线程池是不是好东西?”,而是“这个项目现在有没有必要承担线程池带来的复杂度?”。同样,消息队列、缓存、复杂抽象、分布式锁和各种容错机制本身都不是问题。关键始终是:它们有没有对应当前真实存在的业务压力。
| 维度 | 简单实现(约 500 行) | 过度工程化(约 10,000 行) |
|---|---|---|
| 核心流程 | 抓取 → 临时保存 → 转换 → 正式保存 | 调度 → 任务管理 → 线程池 → Retry → 状态机 → 多层抽象 |
| 排查难度 | 线性路径,几分钟定位 | 故障树扩散,需要理解多层组件 |
| 维护成本 | 低,新人/AI 易接手 | 高,改动前需大量上下文 |
| 适用场景 | 早期验证、小数据量 | 已验证的高并发生产环境 |
500 行变成 10,000 行,增加的不是功能,而是维护成本
如果 AI 写代码几乎没有边际成本,很容易产生一种错觉:多写一些似乎也没什么关系。但代码的成本从来不只发生在第一次生成的时候。以后还需要阅读、修改、测试、升级、排错和交接。一个模块越复杂,后续每一次改动需要理解的背景就越多。
假设某天 SnowLead 出现一个很简单的问题:为什么今天抓到的数据没有进入正式业务表?如果系统保持简单,排查路径很清楚:先看抓取结果,再看临时数据,再看转换,最后看正式写入。但如果中间已经存在完整任务体系,问题马上会扩散。任务有没有提交?Worker 有没有执行?线程池是不是阻塞?是不是正在 Retry?状态有没有更新?Lua 有没有真正运行?异常是不是在某一层被包装掉?事务到底有没有提交?
原本一条线性的路径,变成了一棵故障树。这就是 500 行变成 10,000 行真正带来的成本。不是硬盘空间,也不是 Git 仓库大小,而是:一个开发者需要理解多少东西以后,才敢安全地改这段代码。对于小团队来说,这一点尤其重要。因为未来接手的人可能不是最初的作者,而是另一个开发者、外包团队,甚至另一个 AI Agent。
为什么 Vibe Coding 特别容易把小项目做“大”?
Coding Agent 很擅长继续展开需求。它会主动阅读项目、补边界情况、发现潜在风险,再顺手完善架构。这种能力本来很有价值,但如果没有明确限制,也很容易导致范围膨胀。
你只说“帮我做一个数据导入”,AI 看到外部 API,就会想到失败;想到失败,就会想到 Retry;想到任务执行,就会想到并发;想到并发,就会想到线程管理。这些推理单独看都没有问题。真正缺失的是另外一组信息:这个系统现在一天处理多少数据?有几个用户?有几个数据源?产品已经稳定运行几年,还是刚刚开始验证?团队有几十个工程师,还是只有一两个人维护?
这些背景会直接决定同一种技术究竟是成熟设计,还是没有必要的负担。如果没有主动给 AI 设定规模和复杂度边界,它很容易按照一个比真实业务更大的假想系统继续设计。
AI 写代码,也应该“先从薄到厚,再从厚到薄”
以前常有人用一句话形容读书:先把书从薄读到厚,再从厚读到薄。第一次读的时候,不断接触新的概念、背景和细节,一本原本很薄的书会在脑子里越来越厚。等真正理解以后,又会开始知道哪些内容是核心、哪些只是展开、哪些可以合并,最后重新把它读薄。
我越来越觉得,AI 写代码其实也应该经历类似的过程。Vibe Coding 很适合完成“从薄到厚”的第一步。一开始可能只有一句需求:从外部数据源获取公司数据,并转换成 SnowLead 可以使用的格式。AI 可以迅速把它展开成代码,补充数据结构、错误处理、测试和各种实现细节。过去需要开发者花很多时间才能搭起来的第一版,现在可以很快生成。
问题在于,很多人到这里就停了。代码生成完成,测试能过,页面能打开,于是直接部署。但真正的软件工程工作,可能恰恰应该从这里开始。第二步应该是把代码从厚再读回薄。重新检查整个实现,判断哪些代码真的服务于当前业务,哪些只是为了理论上的未来扩展;哪些异常处理确实值得保留,哪些基础设施只是因为 AI 顺手按照“最佳实践”补上。
以 SnowLead 这次为例,Claude Code 先把一个简单需求展开到了接近 10,000 行。这不代表这 10,000 行全部都没有价值。里面可能有合理的数据结构、日志、测试、异常处理,以及我一开始没有考虑到的真实问题。真正应该做的是第二遍 Review。把线程池、复杂任务管理、过早抽象和当前根本用不到的机制逐一拿出来问:它解决的是已经存在的问题,还是 AI 想象出来的未来问题?如果是真实需要,就留下。如果不是,就删掉。最终留下来的代码可能比最初设想的 500 行更完整,但也完全没有必要接近 10,000 行。

AI 时代,真正稀缺的可能不是生成代码,而是删除代码
过去,过度工程化至少还有一个天然阻力:写这些东西需要时间。一个开发者如果要为小功能创建几十个类、一套线程管理、状态体系和 Retry 机制,至少会因为工作量而重新考虑一下值不值得。现在这个阻力正在快速消失。AI 可以在很短时间内生成大量代码,而且表面质量还可能相当不错。
于是软件开发出现了一个新的问题:当写代码越来越便宜以后,谁来决定哪些代码根本不应该留下?这也是为什么我并不认为 Vibe Coding 本身是错误方向。相反,它很适合快速探索、搭第一版、验证想法,也能帮助开发者发现一些原本没有考虑到的问题。真正危险的是把“生成完成”当成“工程完成”。
一个更健康的工作流应该是:先 Vibe Coding,把需求写厚;再做 Review,把系统读薄。AI 负责快速展开可能性,人负责判断哪些复杂度真的值得进入产品。这时候 Code Review 的目标也会发生变化。过去我们主要检查有没有 Bug、安全问题、性能问题和规范问题。现在还应该增加一个问题:这里有没有根本不需要存在的代码?有时候,一次好的代码检查最有价值的结果,不是再加一个组件,而是发现:这三层其实都可以删掉。
结语:AI 可以把代码写厚,但不会自动帮你把系统读薄
SnowLead 这次的数据导入模块,最后让我重新回到了最开始的问题。它真正需要完成的事情,其实一直没有改变:抓取数据 → 临时保存 → 转换 → 正式保存。Claude Code 可以非常快地把这个需求展开,考虑各种失败情况、扩展方式和工程细节。这种能力本身非常有价值。但生成不应该是最后一步。
AI 把代码从薄写厚以后,还需要有人重新检查,把没有现实依据的复杂度删掉,把重复抽象合并,把未来可能需要、但今天根本用不到的基础设施放回未来。最终留下来的,不一定是最少的代码。而应该是:刚好足够解决当前问题的代码。
过去写软件,难的是把东西做出来。AI 时代,另一个越来越重要的能力,是知道什么东西不应该留下来。所以这次实验并没有让我否定 Vibe Coding。恰恰相反,我仍然会用它快速生成第一版。只是工作流应该完整一点:先把代码写厚,再把系统读薄。AI 可以负责快速扩张可能性。最终决定哪些复杂度值得进入产品,仍然是软件工程中最重要的判断之一。

