让 AI 写一个贪吃蛇游戏,往往很快就能得到可以运行的页面。接着加上得分、加速和暂停,改动看起来也不复杂。

但暂停按钮出现了,游戏就真的暂停了吗?如果只是停止画面渲染,后台仍在移动和判定碰撞,玩家恢复时可能已经输了。如果每次恢复都新建一个定时器,反复操作后,蛇又可能越走越快。

“页面上有这个功能”和“系统满足这项需求”之间,隔着一整套行为约定。AI 加快了实现速度,也让这些未经确认的细节更快进入代码。

我更关注的,是怎样把约定变成可恢复、可检查的依据。下面以贪吃蛇为例,串起需求、设计与验证。

一、让关键约定脱离聊天记录

对话适合探索。需求不明确时,可以通过追问和原型逐步收敛;局部文案调整也可以直接完成。

当功能需要持续修改,单靠聊天记录维护项目状态就容易出问题:早期规则埋在历史消息里,后续要求没有说明哪些约定继续有效,工具读取的代码又可能只覆盖局部。上下文压缩、检索遗漏和相互矛盾的要求,都可能使实现偏离意图。

关键约定应当能够被重新找到:本次解决什么问题,哪些行为必须保持,哪些不在范围内,以及怎样确认完成。执行任务前读取相关内容,并与当前代码核对,才能在换会话或继续迭代时恢复工作。

这也是规格驱动开发(Spec-Driven Development)的起点:让需求、设计和验收依据随着实现一起维护。

二、把“加个暂停”写成行为约定

“游戏支持暂停”至少留下了几个问题:暂停时哪些状态不再变化?还能否改变方向?恢复后是否补算暂停期间的时间?游戏结束后按暂停键会怎样?

EARS(Easy Approach to Requirements Syntax)通过事件、状态和条件组织自然语言需求,帮助我们明确触发条件与系统响应。它可以帮助检查表达,但需求是否符合使用场景,仍需要另外判断。

以单机版为例,可以先作出以下约定:

  • P1:当运行中的游戏收到暂停操作时,进入暂停状态。暂停期间,蛇的位置、食物和分数保持不变。
  • P2:当处于暂停状态时,忽略方向输入;收到恢复操作后,从当前局面继续,暂停时长不计入后续移动。
  • **P3:**当游戏尚未开始或已经结束时,暂停操作不改变游戏状态。

这些约定允许我们判断实现是否正确。例如,P1 约束了游戏状态本身,P2 又明确了输入和时间的处理,避免界面停住但恢复后突然补走多步。

写清楚后也更容易讨论取舍:忽略方向输入是否符合玩家习惯?恢复后立即移动还是等待一个完整间隔?这里选择后者,使恢复时刻成为新的计时起点。重要的是在实现前形成一致判断。

同时,要区分行为要求、实现约束和范围边界。假设这是一个已有游戏:暂停时保持局面是行为要求;沿用现有框架、不新增运行时依赖是本次实现的约束;账号、排行榜和联机功能则不在本次范围内。写明这些内容,可以减少 AI 为了补齐一个功能而顺手重构整个项目、引入无关能力的情况。

三、从行为约定到实现约束

需求确定后,技术设计需要解释怎样保证它成立。

针对暂停功能,可以设计为:游戏状态与画面渲染分离,由一个更新循环推进游戏;暂停时停止推进游戏状态;恢复时重置计时基准,保留原有局面;循环的创建与销毁由同一个模块管理,避免重复启动。

这样,一条用户需求就对应到了明确的实现边界。采用哪种框架或计时 API,可以在这些约束内选择。

如果使用文档组织工作,可以让每份文档各自回答一个问题:

文档 暂停功能中需要记录的内容
requirements.md P1~P3,以及恢复后等待完整间隔的约定
design.md 状态转换、单一更新循环、输入处理与计时基准
tasks.md 实现状态转换和输入限制,处理恢复计时,补齐对应验证

任务不必一次覆盖整个游戏。“完成暂停与恢复,并验证不会产生重复循环”就是可以独立审查的改动。

实现过程中如果发现设计不可行,应说明冲突和影响,再调整设计或需求。完成后,将实际差异同步到文档。cc-sdd 等工具可以辅助组织这一流程,工具之外仍需要有人确认约定与实现保持一致。

四、让测试有能力发现错误

暂停功能可以从几个层次验证。

先检查状态转换:运行状态可以进入暂停,暂停可以恢复,尚未开始或已经结束时不受影响。再使用可控时钟验证输入与计时。

例如,将移动间隔固定为 200ms,关闭加速,并选择不会发生碰撞的局面:暂停后推进时钟 10 秒,蛇的位置、食物和分数都应不变;期间输入一个正常运行时允许的转向,恢复后的方向也应保持原样。恢复后推进 199ms 不应移动,再推进 1ms 才移动一格。

反复暂停与恢复后,还要检查相同模拟时间内的移动次数,以及是否留下额外的活动计时器或更新循环。只断言“每次回调移动一格”并不够,因为重复循环也可能各自满足这个条件。

最后,在浏览器中实际操作按钮和键盘,核对显示状态与游戏行为。浏览器调度会受到负载和后台节流影响,规则计算适合使用可控时钟,实际交互仍需要单独检查。

这里最容易产生误导的是:让 AI 写完实现,再根据实现生成测试。两者可能共享同一个错误。

例如,我们另行约定加速规则:初始移动间隔为 200ms,每获得 5 分减少 20ms,最低为 80ms。数值仅用于演示。

这里的分数指累计得分,达到一个完整的 5 分档位才加速。假设实现误写为“每 4 分加速一次”,结果会出现以下差异:

分数 按需求应得到的间隔 错误实现的结果
4 200ms 180ms
5 180ms 180ms
8 180ms 160ms
10 160ms 160ms

只检查 5 分和 10 分,测试仍会通过。检查阈值之前及两个阈值之间,才更容易暴露错误。如果测试直接把错误实现的结果当作预期,则连这些用例也失去作用。

同样,最低间隔也应单独验证:25 分时为 100ms,30 分时达到 80ms,35 分时仍保持 80ms。阈值附近与达到上限、下限后的行为,都容易被只覆盖常规流程的测试遗漏。

因此,验收预期应从需求独立推导。审查测试时,我更关心它能否区分正确与错误的行为,而不只是覆盖了多少行代码。测试仍可能由 AI 辅助生成,关键是预期值有独立依据,并经过审查。

五、规格清晰,也可能做错产品

规格让错误更容易被发现,但一份清晰的错误需求,也可以被 AI 高效地实现。

回到 P2:如果玩家希望暂停时提前调整方向,“忽略方向输入”就可能让操作不符合预期。实现完全符合规格,测试也全部通过,仍然需要通过原型和使用反馈决定是否修改规则。

这涉及两种不同的判断:实现是否符合约定,以及约定是否解决了使用者的问题。前者可以借助测试验证,后者需要业务确认、用户反馈和实际使用结果。

我的取舍是,先确认会影响体验和系统边界的决策,再细化实现。对于仍有争议的行为,将其标为待确认项,通过小范围原型验证,而不是让 AI 把推测扩写成一份看起来完整的规格。

六、需求变化可能触发设计重审

如果继续将游戏扩展为共享场景的多人联机版,暂停就从一个本地状态变成了协作规则。

一名玩家暂停时,其他玩家是否继续?暂停期间是否保留碰撞实体?断线是否等同于暂停?这些问题会影响公平性、服务端状态和恢复流程,原来的 P1~P3 已经不够用了。

在这个设计推演中,需要先重新确认行为约定,再确定谁裁定关键状态,以及如何将输入和结果同步给参与者。采用权威服务器时,暂停等关键操作也需要经过服务端规则判断,客户端展示应与裁定结果保持一致。

WebSocket、网络框架和序列化格式的选择,随后依据通信频率、房间规模、允许延迟和压测结果比较。规格变化可能使旧设计失效,架构决策也需要随之更新。

七、控制规格的维护成本

规格与代码共同演进,也会共同产生维护负担。文档描述旧行为、代码实现新规则时,下一次 AI 修改可能继续扩大偏差。

行为变更的审查应同时检查需求与测试。仍待确认的假设需要标明,已经放弃的决策不应继续作为实现依据;任务开始前,也应核对相关规格与代码是否一致。

一次修改的完成记录,可以对应到 P1~P3,写明实际执行了哪些测试、结果如何、哪些浏览器交互已经检查,以及仍有哪些未验证项。任务列表上的勾选不能代替这些证据;验收不通过时,应先判断是实现偏离约定,还是约定本身需要修改,不能直接把预期改成当前代码的输出。

过程的规模可以按风险调整。局部文案修改通常只需要任务描述和页面检查;权限、支付、数据迁移或跨服务协作,则值得投入更明确的行为约束和失败处理。文档应当帮助减少误解,而不是成为每次小改动都要跨越的门槛。

AI 降低了把想法转成代码的成本,也降低了把未经确认的假设转成代码的成本。规格、实现和验证之间需要保留能够相互核对的依据;熟悉业务、代码与运行时的人,仍要判断交付是否解决了问题,以及这些证据是否足够可信。

参考资料