为什么我不认同规格驱动设计

规格驱动开发(SDD)是新的炒作。总体承诺很诱人:你用AI规划智能体写一个详细的规格说明,交给AI开发智能体,然后看着它完美地构建你的功能。

为什么我不认同规格驱动设计
AI模型价格对比 | AI工具导航 | ONNX模型库 | Vibe Coding教程 | Tripo 3D | Meshy AI | ElevenLabs | KlingAI | ArtSpace | Phot.AI | InVideo

这听起来像捷径吗?对我来说是的。在这个行业工作了25年多,它确实让我联想到一些东西。它看起来与瀑布方法如此相似,只是现在有了AI的帮助。但我认为今天它与30年前的瀑布方法有着一些相同的缺陷。

在Martin Fowler的一篇经典文章《设计已死?》中,他讨论了"计划设计"和"演化设计"之间的区别。SDD感觉像是回到了计划设计。它假设我们可以在构建系统之前了解关于它的一切。

在任何规模可观的项目中,你只有开始编码时才会发现真正的障碍。如果你的项目小到可以放入LLM的上下文窗口,SDD可能有效。对于其他一切,详细的规格说明最终会撞上未预见的技术现实的墙壁。

免责声明:我在一些评论之后添加了这些内容。当我在本文中谈论规格说明时,我主要指的是技术规格,而不是功能规格。功能规格很少因技术问题而改变,需求拆分则是一个完全不同的话题。在本文中,我主要关注的是技术规格,即如今在AI"计划模式"中构建的那种。

1、"简单"功能的陷阱

当我在2000年开始工作时,敏捷正在兴起。它教会我们一个残酷的真相:短迭代是捕获错误假设的唯一方法。SDD假设我们可以在构建功能之前了解关于它的一切。

让我们想象一个我们系统中非常常见的任务:处理一个新的支付webhook。

它看起来很简单。你为你的AI智能体写一个清晰、精确的规格说明。以下是我们可以写的一个简短摘要:

- 创建一个POST端点 /webhook
- 从JSON负载中提取 order_id,否则返回400 Bad Request。
- 在数据库中查找订单,如果不存在则返回404 Not Found。
- 将其状态更新为'PAID'(pay_type_id = 3)——触发'ReceiptEmail'类。

AI完全按照你的要求做了。它写出了干净的代码,遵循你的AGENTS.md和测试。你将其部署到你的预发布环境。你感觉非常高效。

然后,现实打击了你。

你做一个手动集成测试,立刻注意到客户收到了三封收据邮件。发生了什么?你调试代码并意识到支付提供商期望在500毫秒内收到 200 OK 响应。如果没有收到,它会重试。你的端点花费了600毫秒,因为它是同步发送邮件的,或者因为预发布服务器很小,被其他开发人员的工作挤满了。

完美的规格说明没有考虑到网络延迟、重试或竞态条件。为了修复它,你现在需要转向。你意识到你需要一个完全不同的流程:

  • 一个可以存储负载以便稍后处理的队列,并立即返回 200 OK
  • 一个后台进程来异步更新和发送邮件。
  • 一个幂等键来防止重复。

你原来的规格说明现在没用了。初始代码必须被抛弃。你只有通过与环境的实际交互才发现了真正的架构需求。

当然,这只是一个例子,但它展示了一个简单的任务如何容易变得复杂。是的,我知道你们中一些读者会认为自己会做得更好,觉得自己应该了解架构,经典的"你做错了"。好吧,让我告诉你,没有人知道一切,连LLM也不知道。你的团队也不是充满了能预测每个可能边缘情况的高级工程师。

2、Token税 vs. 重构税

有了AI,预先设计引入了一个新的经济问题:Token税。

你花了token来解释Webhook规格说明。你花了token来生成有缺陷的代码。当现实迫使你转向时,你又付了一次税,向AI解释队列、后台工作者和幂等性。这是低效的。

我更喜欢将这些token投资于 重构税。从小处开始。让AI构建一个简单的概念验证(POC),只记录传入的负载。运行它。观察提供商的实际行为。一旦你了解了现实,使用AI来持续重构——提取邮件逻辑、添加队列并在过程中清理代码。这保持代码库健康,并让架构自然地涌现。

3、地图,而不是蓝图

当我写设计文档时,我喜欢Simon Brown(以C4模型闻名,这是一种友好的软件架构图表方法)的Google Maps类比。

你需要缩小来看上下文。你不需要一张显示每一根草的详细地图。

糟糕的设计文档(SDD风格):

WebhookController将解析JSON,验证签名,更新MySQL中的 status 列,并实例化Mailer服务。

好的设计文档(Fluid风格):

支付事件通过webhook接收并放置在队列上。后台工作者处理订单履行和邮件分发,以确保幂等性。

关注重要的决策。为什么我们使用队列?这些模块如何解耦?将逐行实现留给代码。

4、使用适应度函数,而不是规则

如果你没有详细的规格说明,如何防止AI制造混乱?你使用适应度函数。

适应度函数是自动化的治理。你不是在文本提示中要求AI"请保持HTTP层与领域解耦"。你写一个强制执行此要求的架构测试。我以前谈论过这个,比如在这篇文章中。

我们如何防止前面的情况发生?我们写一个快速测试,当像 ReceiptEmail 这样的慢速服务直接在HTTP控制器中使用时立即失败(可选地,我们可以在服务层本身进行快速检查,要求在后台任务中才能继续)。

你不是在写规格说明;你是在设置一个硬边界。未来的规格说明不再需要担心这个问题。AI可以在这些墙壁内自由探索和编写代码;当其POC中抛出适当的错误消息时,它会自动转向。

注意:有些人认为测试驱动开发可以用于这些场景。我不太同意。我发现TDD过早地固定了你的实现;它非常适合小型bug修复,但在探索新功能并需要喘息空间时就不那么好了。但那是另一个话题。

5、我目前的答案:Fluid Design

那么,SDD的替代方案是什么?我喜欢称之为Fluid Design。

  • 设计文档,而不是规格说明: 只记录高层决策、边界和模式。
  • 主动原型设计: 不要规定解决方案。向AI询问实现选项。构建小的POC来测试未知因素(如webhook延迟),然后再承诺于某个架构。
  • 实时更新: 将你的设计文档视为活记录。每次你在代码中发现新的约束时,更新文档。
  • 你是船长: 你掌舵。你不是把地图交给AI然后去睡觉。你根据代码告诉你的信息实时调整航向。

归根结底,唯一重要的规格说明就是代码。其他一切都只是猜测。从简单开始,持续重构,让你的架构自然演化。


原文链接: The Spec is the Code: why I don't buy Spec-Driven Design

汇智网翻译整理,转载请标明出处