Ohhnews

分类导航

$ cd ..
foojay原文

从规范驱动开发到Java项目中的活规范

#规范驱动开发#java#ai辅助开发#活规范#软件架构

从规格驱动开发到 Java 项目中的活规格

编程代理已经改变了我们生产软件的速度。但更快地生产代码并不自动意味着生产更好的软件。

随着我在真实世界软件开发中越来越频繁地使用代理,一个问题变得日益重要:我们如何给予代理足够的自由度,使其发挥作用,同时又不丢失工程意图?

试图控制代理生成的每一行代码,会抹去它所能提供的许多价值。到那时,直接自己编写代码可能反而更快。

另一方面,仅仅描述一个结果,并接受任何能产生该结果的实现,会让我们危险地滑向 氛围编程

在这两个极端之间,一定存在某种中间地带。

规格似乎提供了一个有希望的答案。它们可以在实现之前让意图变得明确,为代理设定边界,并提供用于检验所得软件的依据。

这就是我们所说的 规格驱动开发

但在实验这种方法之后,另一个问题开始变得比“如何用规格驱动一次变更”更有趣:

变更完成之后,规格会变成什么?


规格驱动开发的承诺

对这一旅程产生强烈影响的方法之一,来自我的挚友、Oracle ACE 和 Java Champion Loiane Groner 的文章“Vibe Coding,但生产就绪:面向 AI 辅助开发的规格驱动反馈回路”

吸引我注意的是将工程纪律引入 AI 辅助开发,同时又不放弃编程代理所能带来的生产力的理念。

这个循环从产品意图到实现与验证逐步推进:

[LOADING...]

我们并没有给它一个孤立的提示并立刻请求代码,而是让每一步都为下一步产生上下文。

在与社区中的一位朋友、同为开源贡献者的 Matheus Oliveira 交流后,我们决定将这些想法付诸实践。

我们将这个循环物化为一组 Agent Skills,命名为 SLDD

而第一个重要发现,仅仅是这样一套工作流从头到尾运行起来。

工作流给了我们结构。假设可以在实现之前被质疑,技术决策可以被审查,测试成为意图与代码之间路径的一部分,而不是事后再添加的东西。

但实验一个想法,不仅仅告诉我们它是否有效。

实验会暴露出我们未曾预见到的情况,更重要的是,它让我们能提出新的问题。这正是 SLDD 发生的事。

对此,我特别感谢 Loiane。

她的文章不仅仅向我们介绍了一种有趣的方法,还鼓励我们去实验它。一些最终塑造了这次旅程的问题,正是因为我们先有机会把这些想法付诸实践才得以显现。

谢谢你,Loiane,分享你的想法并激励我们去实验它们。


我们通过将其付诸实践所学到的东西

随着我们继续用 SLDD 做实验,并探索其他规格驱动开发方法,包括 OpenSpecGitHub Spec Kit,我们开始更少关注每个工作流中的单个步骤,而更多关注它们背后的模型。

在变更发生时,规格可以极其有用。

它们让意图清晰,保留决策,为编程代理提供上下文,并在实现偏离正确方向太远之前创造检查点。

但这些好处也伴随着权衡。

更多的规格与设计产物,意味着需要生产、持久化、检索和解释更多的上下文。更多的工作流步骤也可能意味着更多的人工转换:生成、审查、批准、继续。

在某个节点,我们在自己的实验里注意到一种令人不安的情况:

人类正在成为工作流状态机的一部分——换言之,成为流程的瓶颈。

我们并不是只在需要工程判断时介入,有时我们只是在让流程从一个状态移动到另一个状态。

但还有一个更重要的问题出现在工作流完成之后。

设想一个规格成功地将一个功能一路带到生产环境。

几周后,有人直接在实现中修复了一个小 bug。

测试通过,代码被审查,变更被部署。

但最初的规格从未更新。

软件已经演进,而它的规格仍在描述过去的某个现实。

一次变更的生命周期比软件本身短得多,而软件往往处于持续演进之中。

这说明,一个规格可以在驱动变更时表现出色,却在该变更成为系统一部分之后丧失其用处。

而这又引出了另一个有趣的问题:

当一个变更成为软件的一部分之后,规格如何能保持有用?


当规格不是真相来源时

规格驱动开发往往围绕这样一个观点来呈现:规格成为 真相来源

作为软件意图的表达,这可能是一个有趣的想法,但有一个略微令人不安的实际细节:

规格不会在生产环境中运行,代码才会。

这并不会使规格无用。它只是意味着规格代表着另一种真相:

  • 规格描述的是预期行为。
  • 代码实现的是实际行为。
  • 测试提供了连接二者之间的可执行证据。

这让我们走向一个变得愈发重要的模型:

[LOADING...]

而且没错,箭头很重要,因为软件在两个方向上都会演进:

  • 有时,实现意外违反了一项既有需求,代码需要向规格收敛。
  • 另一些时候,实现有意演进,规格需要跟随这一变化。

这个模型能让规格变成我们可以称之的 活规格

这并不意味着规格会“神奇地”自动更新。漂移仍然可能发生。这个想法有一个更务实的目标:

让分歧可见,让收敛变得廉价。

这改变了我们寻找的东西。

我们希望规格离实现足够近,让开发者和代理都能自然找到它们。

我们希望需求精确到可测试。

我们希望规格描述的是 软件组件当前的承诺,而不仅仅是保存创造它的那次变更的历史。

正是在那时,我找到了 SBCE。


SBCE:让规格更靠近代码

SBCE,发音为 “space”,是一种规格驱动开发工作流,由 Java Champion Adam Bien 创建,围绕 Boundary-Control-Entity(BCE) 架构风格构建。

它的一个想法立刻与我们一直追问的问题产生了联系:

规格与代码共存。

在 SBCE 中,每个 业务组件(BC)都把自己的规格放在所在包中的 package-info.java 里,以 Markdown Javadoc(Java 23+)形式呈现。

这省去了在与代码相距很远的独立结构中维护一棵并行规格树的需要。

当然,这不会神奇地让文档变得可执行,也不会消除漂移,但它改变了规格存在的位置。

规格成为与代码相同结构邻里的一部分,让开发者和编程代理在处理该组件时更容易找到相关片段。

另一个重要想法是,通过受 EARS 启发的结构,来表达业务组件的行为,使其 可测试、可追溯

在 SBCE 中,这些需求用英语表达。

相比这样一个模糊的需求:

结账应该正常运作。

我们可以表达可观察的事物:

当对一个空购物车请求结账时,业务组件应拒绝该请求。

通过为该需求分配一个稳定标识符,我们可以建立如下关系:

[LOADING...]

代理仍然可以自由思考实现,但预期行为现在有了确定性的验证边界。

SBCE 还为我们强化了另一条教训:

简洁很重要。

它的主工作流刻意保持小巧,提供两种模式:

  • /sbce new
    • 声明一个 业务组件,将其规格写入包中的 package-info.java
  • /sbce apply
    • 致力于收敛规格与代码之间检测到的漂移。

其他关注点,比如编码约定,可以委托给可组合的 skills,而不是不断扩大一套庞大的指令集。

在探索 SBCE 的过程中,我有机会与 Adam 讨论,如何将这个想法带入使用 Spring Boot 等非 MicroProfile 技术栈的 Java 项目。

那次对话非常有趣,并澄清了关于 SBCE 的一个重要区别:

SBCE 在架构上是有立场的,但在技术上可扩展。

BCE 提供了 SBCE 内部的架构基础以及“业务组件”的含义。

Adam 描述了如何利用流程中的 控制反转 概念,通过组合额外的 skills 来适配其他技术栈。

这意味着支持 Spring Boot 并不一定需要改变 SBCE 本身。一个专门针对 Spring Boot 的 skill 可以将 BCE 映射到该技术栈。

但这也揭示了我们真正的问题。

我们处理的大多数 Java 项目都是 棕地 项目。此外,其中许多应用使用 Spring Boot,并且 并没有采纳 BCE 作为其主要架构。

有些采用 按特性分包,有些采用 按层分包,在许多情况下我们还会看到多年来积累的混合架构决策。

为了把这些想法带到这些项目中,我们需要做的不仅仅是改变技术栈:我们还需要应对 BCE 之外的架构。

这创造了一个新的实验机会。


SDD4J:适配 Java 项目中的多种架构

我们想探索的问题相对简单:

如果“业务组件”的含义能够适配 Java 项目的架构,会怎样?

我们没有让主规格工作流去理解每一种可能的架构,而是引入了 架构适配器 的概念。

概念上:

[LOADING...]

核心仍然关注规格、可测试需求、可追溯性和收敛。

适配器回答一个架构问题:

这个项目中的业务组件在哪里?

在 SDD4J 中,业务组件代表一种业务能力。不同架构适配器之间变化的是,如何在项目现有结构中找到并界定这种能力。

  • 在 BCE 中,BCE 结构本身已经提供了边界。
  • 按特性分包 中,特性包本身可以自然地提供边界。
  • 按层分包 中,业务能力可能分布在多个技术包中,需要不同的映射策略。

这在 棕地 开发中尤其重要。

我们不希望采用活规格意味着必须先重新组织现有应用。

因此,工作流应该按项目本来的样子去识别项目。

基于这个模型,我们创建了 SDD4J 工作流。它提供了一小组操作:

  • /sdd4j setup 建立项目上下文,比如架构、规格使用的语言。
  • /sdd4j new 在不修改代码的情况下,为一个新业务组件及其可测试需求创建规格。
  • /sdd4j apply 查找规格、测试与实现之间的分歧,并解决检测到的漂移,使它们重新对齐。
  • /sdd4j verify 查找可执行证据,证明声明的需求仍然由测试和软件行为所代表。

setup 中配置的语言也用于书写 EARS 需求。

这样一来,例如配置为 PT-BR 的项目,就可以让规格和需求保持在该语言中。

前面提到的同一条需求,因此可以在配置为 PT-BR 的 SDD4J 项目中表达为:

Quando um checkout for solicitado para um carrinho vazio, o Business Component deverá rejeitar a solicitação.

我们保留了同样的意图:

[LOADING...]

但使用项目配置的语言。

这些操作也并非设计成一次强制性的状态机,要求每次代码变更都必须经过它。

一个小 bug 修复,可能不值得一整套复杂的代理工作流。

有时,开发者应该直接做修改。

有时,代理应该实现它。

还有些情况下,代理最有用的角色可能是事后再检查结果,识别缺失的测试或规格漂移。

目标不是制造仪式。

目标是保留足够的结构,让人类和代理都能理解 一个业务组件承诺做什么,并验证软件是否继续兑现这一承诺。

其他 Agent Skills 可以围绕这个核心进行组合,为特定项目提供工程护栏。

其想法不是去教代理那些它已经知道的关于 Java、Spring、测试或软件设计的东西。

而是要提供 增量:那些对该项目真正重要的决策和约束。

给代理足够的自由去推理。

给它足够的约束,让它与项目保持一致。

并让变更保持足够小,让人类工程师仍然能够理解和审查它们。


没有银弹

把这个故事讲成一个线性进展是很容易的:我们实验了一种方法,发现局限,发现另一种,改进了那个想法,最终到达 SDD4J。

那会是一个错误的结论。

我们并没有消除权衡。

我们选择了不同的权衡。

SBCE 刻意以 BCE 为锚点,同时保持技术栈中立,这意味着它可以用于 Java 技术栈,也可以用于其他技术栈,比如 Web Components。

这给了它关于业务组件的清晰定义,同时允许通过 skills 去适配与特定技术相关的关注点。

SDD4J 刻意以 Java 生态系统 为锚点,同时支持多种架构风格。

这给了我们像 package-info.java 和 Javadoc 这样的原生机制,同时允许通过适配器去改变业务组件的架构解释。

两者并没有绝对优劣之分。

对于一个已经围绕 BCE 组织起来的项目,无论技术栈如何,SBCE 可能是更简单、更自然的选择。

对于一个采用不同架构的既有 Java 项目,SDD4J 可能是一个有趣的选择。

归根结底,这是不同的权衡。

而其他 SDD 工作流通过选择不同约束,解决了问题中的其他部分。

这就是软件工程。

灵活性并不自动优于约束。

更多自动化并不自动优于人类判断。

更多上下文并不自动意味着更好的上下文。

规格也不会仅仅因为我们称它为 真相来源,就自动变成真相。

我们开始时问的是,规格如何能更好地借助编程代理驱动软件开发。

实验这个问题,把我们带到了一个略有不同的地方。

今天,我发现更有趣的问题是:

在软件持续演进的同时,我们如何让软件意图保持可理解、可验证,并且贴近它所描述的行为?

这就是我们所说的 活规格

SDD4J 是我们在这个方向上的当前实验,我们邀请你与我们一起尝试。

该工作流可在 soujava/agent-skills 仓库中找到,你可以将它用于任何 Java 项目。

它是开放源代码且免费使用的,我们欢迎贡献 :smile:。

我相信它会继续演进。其中一些想法可能被证明有用,另一些则未必。但最重要的是,我希望它能帮助我们继续探索这些问题。

在一个编程代理和模型不断改变我们构建软件方式的世界里,SDD4J 本身最终也可能过时——这也完全没问题。工作流应当随着我们的工具、模型和工程实践一同演进。

工作流可能是暂时的,但软件意图必须比任何工作流都更持久。


你怎么看?

这篇文章代表了我们目前实验所到达的地方:并非一个确定的答案

这正是我想听到你经验的原因。

你是如何在编程代理中使用规格的?

你是把规格当作 真相来源,还是也遇到过规格与实际运行在生产环境中的代码之间的漂移?

你实验过规格驱动开发、SBCE、OpenSpec、Spec Kit,还是完全不同的方法?

要不要在现有 Java 项目中试试 SDD4J?它可能对你有用,也可能没有。无论怎样,我希望它能激发新的想法。

而也许最让我感兴趣的问题是:

“活规格”对你来说意味着什么?

请在评论中分享你的经验、不同意见、实验和教训。这些不同的视角,正是让这场讨论有价值的原因。

如果这篇文章给了你一些值得思考的内容,把它分享给另一位正在实验 AI 辅助软件开发的开发者或团队。

也许下一个有趣的想法,就会从那次对话开始。

本文 从规格驱动开发到 Java 项目中的活规格 最初发布在 foojay