从 25 年初大模型爆发开始,我的开发工作流就变成了下面这样:
- 先自己实现功能。
- 同时让 2~3 个大模型去
Review,然后我再逐一审核它们报告的问题是否真实存在(事实上有很多误报)。 - 在
QA测试期间,再整体Review一遍。
后来开始使用 Cursor Tab 来加速整个写代码的过程,但是整个工作流的本质并没有发生什么改变。
随着越来越多的文章开始“嘎吹” Vibe Coding,我也浅尝辄止地试过将整个策划案丢给大模型,让它直接基于现有的代码去实现,结果它往往会将代码弄得一团糟。
在 25 年 12 月份时,我突然产生了一个好奇:
如果我不是在生产环境使用 Vibe Coding,如果我对代码质量的要求没有那么高,那 Vibe Coding 的上限到底在哪里?
为了避免我过度干涉大模型的决定,而只是在大方向上去当舵手,我特意选择使用 Cocos 而不是 Unity 来做一个塔防游戏(因为我真的会 Unity :D)。
通过一个月的业余时间,我完整地做完了一个塔防游戏的一个关卡,整个项目大概 2 万行代码左右(不算太多,但也不算太少)。虽然只有一个关卡,但整个逻辑代码是完整的,多个关卡只是配置和美术素材的事。
整个功能包括:打表工具、资源管理、拖动放置、正常攻击效果、战斗计算、肉鸽机制、战报保存和回放等。回放只是预留了接口还没有测试,不过整个设计都采用了定点数和逻辑帧来实现,理论上不会有太大障碍。
我全程只调整了一个坐标系算法(大概 100 行),除此之外都是我提出想法或算法,代码由大模型实现。
我不禁在思考,这次还算成功的 Vibe Coding 体验和以前失败(直接丢策划案给大模型)的体验,最大的区别是什么?
除了我不再苛责代码质量、整个项目是从 0 开始之外,是否还有别的我没注意到的细微差异。
最终我还是找到了一些关键。
首先,这个项目完全是大模型实现的。
因此一些脚手架代码或者说是框架代码也是大模型实现的。
从本质上来讲,大模型既然选择了这种实现方式,就代表它被训练过,也代表它知道如何在这种体系下实现业务代码。
因此在写业务时只要稍加约束,就不会偏离主题太多。
而我们生产环境的代码一般都是“祖传代码”,这种代码可能从来没有被大模型见到过,更别提如何在这种体系下工作。
这就会导致生成的代码很容易不按祖传套路出牌,往往需要更强的 Prompt 约束。
其次,这次实验过程中,我并不是直接告诉大模型给我实现一个塔防游戏。
说到这里,我对所有的鼓吹一句话生成一个App的人都非常反感。
一个没有任何约束的目标,本质上只是生成了一个大模型见过最多的同类型 App,这代表市面上已经有了成千上万的同类型 App。
这种 App 就算做出来有什么意义呢?除了烧掉你的钱包之外,你并不敢也不会把这种 App 发布到生产环境,即使它完全没有 Bug。
在整个塔防游戏的实现过程中,我是完全按正常的开发流程进行的。
比如我让它先实现地图加载,再实现敌人生成,然后实现拖放防御塔。
所有的功能实现都源于我的拆解,我从来不会让它一次性做两个功能(不是两个文件,有时一个功能需要修改数十个文件,这并不影响大模型的正确性)。
这个项目最初我只是想尝试使用 Vibe Coding 做一个最简单的游戏,我脑子里对整个蓝图并没有太多完整的概念,都是做完一步,再去看下一步做什么。
实践下来看,这反而促成了大模型的稳定输出。
最后,是策划案自身的问题。
之前的失败,固然可能是因为当时大模型还不够强,但更多还是策划案本身的问题。
- 第一,策划案并不完整,很多时候策划会依赖于程序的“潜规则”或约定。
- 第二,策划案描述的虽然是一个完整功能,但其内部往往包含非常多的复杂约束逻辑,有时一个策划案就把大模型的上下文吃完了。
- 第三,策划案描述的其实是策划对于游戏运行规则的蓝图,它和代码实现之间其实隔着一道“天堑”。
再加上项目中的祖传框架,大模型其实很难越过这道天堑。
我想,只要我能帮助大模型越过这个天堑,将 Vibe Coding 用于生产环境也许并不是不可能。
刚好,我需要做一个新需求,不算太复杂,但是自己吭哧吭哧写,怎么滴也得 2~3 天。我打算使用 1~2 天来尝试一下,就算失败了,我稍微加加班也能弥补这两天的损失。
为了增强对大模型的约束,这次我拆解得比做塔防游戏时更细:
- 我先根据策划案设计好数据结构。这时就体现出来“程序 = 数据结构 + 算法”的金玉良言了,我的数据结构本身就对大模型形成了一个强有力的约束。
- 然后再让它根据数据结构,实现数据库存储、加载等基础功能。
- 再根据策划案拆解出 10+ 条客户端消息,定义好每条消息需要实现的功能。
- 让大模型结合已经定义好的数据结构,按我定义好的工作流(先去
Google Protobuf中定义协议,再去生成协议代码,最后实现协议处理)实现功能。
需要说明的是,我并没有一次性将所有消息和功能定义都发给大模型,而是每实现完一个消息,再去实现下一个。为了让大模型严格遵循项目风格,我在每次使用大模型实现功能时都会给出两样东西:
- 本次实现的功能是什么。
- 与本次实现功能最相近的代码(让大模型参考风格和逻辑取舍)。
最终,我大概花了 1 天半的时间就实现了所有功能。
当然,因为我实现的是服务器功能,一旦产生数据 Bug 之后,影响会比客户端更恶劣,因此我最后又花了 2 天半做了全面仔细的 Review。
老实讲,其实我一个 Bug 也没发现,只是发现了少量代码冗余,和有一处模块间的耦合没有适配。
PS. 事实上这篇文章是上线过了一个月之后,我才敢发出来的 ^_^!
了解 重归混沌的BLOG 的更多信息
订阅后即可通过电子邮件收到最新文章。