重构软件开发的权力格局:从TDD、DDD到AI时代的SDD,哪种才是你的工程哲学?

十年前,TDD让我们重写测试如同推翻巴别塔;三年前,DDD试图用领域语言统一产品与技术的分歧;今天,SDD直接把“规范”变成可执行的源代码,AI成为这个新世界的翻译官。

上周团队来了个新人,对着我们基于DDD设计的微服务架构一脸困惑:“这服务划分的依据是什么?这个聚合根的边界怎么定的?”我让他看领域文档,他翻了几页:“这些和代码对不上啊,上次重构后就没更新了吧?”

这一瞬间,我突然意识到,我们可能站在又一个范式革命的临界点。


01 演化史:从测试优先到规范即代码

2000年左右,Kent Beck提出TDD时,它的核心很激进:先写测试,再写实现。这在当时绝对是个异类——哪有还没写代码就先想怎么测试的?

可问题恰恰在这里:传统开发中,测试往往是最后一步,经常被挤压甚至忽略。

TDD的诞生,本质上是对“测试是二等公民”这一现状的反抗。它不只是个技术实践,更是一种开发哲学:通过可执行的测试用例来明确需求

2000 左右:TDD
测试驱动开发(先写测试,再写实现)

2003:BDD
行为驱动开发(Given-When-Then)

2003:DDD
领域驱动设计(限界上下文、通用语言)

2010s:敏捷 & 微服务落地
与TDD/DDD深度结合

2020+:SDD & AI 时代
规范即代码,AI生成实现

大约2003年,Dan North在TDD基础上提出了BDD。他发现许多开发者和业务人员对“测试”这个词有心理抵触,于是把焦点从“测试”转移到“行为”上。

BDD的核心是用业务人员和开发者都能理解的自然语言描述系统行为。那时候流行的工具是Cucumber,那些“Given-When-Then”的语句,我第一次见到时觉得“这玩意儿太形式主义了吧”。

有趣的是,DDD在同一时期也在兴起。Eric Evans在2003年出版的《领域驱动设计》中,直击了当时企业软件开发的核心痛点:业务逻辑与技术实现之间的巨大鸿沟

回忆一下:你们团队的产品经理有没有说过“这个需求很简单,就加个按钮”?技术实现时才发现背后涉及三个服务的数据一致性?

DDD的核心理念是用“通用语言” 在业务和技术团队间建立沟通桥梁,通过战略设计和战术设计,让软件结构反映业务本质。

02 SDD:AI时代的新范式

到了AI编程时代,问题变得更加尖锐。SDD正是在这样的背景下崛起的

SDD的核心是规范即真理,代码只是规范在特定语言和框架中的表达。

这不只是个技术上的小调整,而是权力关系的根本反转

几十年来,代码一直是软件开发的核心资产。PRD、设计文档这些“规范”类的东西,往往是写出来就没人看,代码改了一轮又一轮,文档还停留在最初版本。

SDD打破了这种局面:规范成为单一事实来源

更重要的是,现在的AI已经能理解并实现复杂的规范。规范不再只是给人看的文档,而是可以直接“喂”给AI生成代码的“可执行蓝图”。

03 理念差异:四种方法的核心对比

方法 核心关注点 核心产出 适合场景 典型工具/框架
TDD 代码正确性 测试用例 + 实现代码 函数/模块级别开发 JUnit, pytest, Mocha
BDD 系统行为 行为描述 + 可执行规格 端到端功能验收 Cucumber, Behave, SpecFlow
DDD 业务领域建模 领域模型 + 限界上下文 复杂业务系统设计 无特定工具,是一套方法论
SDD 规范完整性 可执行规范 + 生成代码 AI辅助的现代开发 OpenSpec, Spec Kit, BMad

TDD像是在拼图:你先确定每一块的边缘形状(测试),再去找能匹配的图案(实现代码)。

它的局限性也很明显:测试用例毕竟只是“点”的验证,难以确保整体业务逻辑的连贯性。

BDD前进了一步,它关注的是“用户能看到什么,系统应该有什么行为”。

但我得实话实说:在不少项目中,那些精心编写的.feature文件最后变成了另一种形式的“过时文档”,因为维护成本太高。

DDD试图解决更深层的问题:如何让软件架构反映业务本质?它提出了限界上下文、实体、值对象、聚合、领域服务等一系列概念,试图在业务复杂度和技术实现之间建立映射关系。

实际项目中,DDD最难的不是概念理解,而是如何保持领域模型与代码实现的一致性

模型讨论会上画了一堆精美的图,两周后代码就偏离了最初的设计——这场景熟悉吗?

SDD采取了截然不同的路径。它不试图让代码去“反映”某个模型,而是让规范成为事实来源,代码只是规范的“快照式实现”。

说得更直白点:在SDD中,如果你要改功能,不是直接改代码,而是改规范,然后重新生成代码

04 实战场景:何时用哪种方法?

适用TDD的场景

我一般会在这些情况下用TDD:

  • 算法实现:比如写个复杂的排序算法或者数据处理逻辑
  • 工具库开发:确保每个函数在各种边界条件下都正确
  • 遗留代码重构:加测试保护已有的行为
# 典型的TDD流程示例:实现一个字符串计算器
# 1. 先写测试
def test_empty_string_returns_zero():
    assert add("") == 0

def test_single_number_returns_itself():
    assert add("1") == 1

def test_two_numbers_comma_delimited():
    assert add("1,2") == 3

# 2. 实现最简单的可通过代码
def add(numbers: str) -> int:
    if not numbers:
        return 0
    if "," in numbers:
        nums = numbers.split(",")
        return int(nums[0]) + int(nums[1])
    return int(numbers)

适用BDD的场景

BDD更适合这些场景:

  • 团队有业务分析师参与:需要共同理解需求
  • 新功能验收:确保开发的功能符合业务期望
  • 端到端测试:用户关键路径的验证

适用DDD的场景

DDD的用武之地:

  • 业务复杂度高的系统:电商、金融、供应链等领域
  • 长期演进的项目:需要清晰的架构应对变化
  • 多团队协作:需要明确的上下文边界

适用SDD的场景

根据参考材料,SDD特别适合:

  • AI辅助开发项目:让AI在清晰的规范约束下工作
  • 增量开发场景:在现有系统上添加功能,需要AI理解项目现状
  • 需要高度一致性的项目:规范作为单一事实来源,确保文档与代码同步

一个典型的SDD工作流,就像材料中描述的OpenSpec流程:

  1. 创建变更提案:明确为什么改、改什么、影响范围
  2. 编写规范变更:在specs/目录下定义新的能力需求
  3. 生成实现计划:AI根据规范创建技术设计
  4. 实现并归档:完成代码后,规范自动更新

05 范式融合:实际项目中的混合策略

在真实项目中,我很少只用一种方法。更常见的是根据项目的不同阶段和不同部分,选择合适的方法组合

以我们现在做的一个电商平台为例:

  • 支付领域:用DDD建模,因为业务规则复杂,有清晰的限界上下文(订单上下文、支付上下文、库存上下文)
  • 支付接口:用TDD确保各种异常情况处理正确
  • 用户下单流程:用BDD描述关键路径
  • 新的推荐算法模块:尝试用SDD,因为可以让AI在明确的性能规范和算法约束下生成代码

更关键的是,这些方法之间并不互斥。完全可以在SDD的规范中,包含TDD的测试要求,描述DDD的领域概念。

06 SDD的挑战:理想与现实的差距

别把SDD想得太美好,实际落地有不少坑:

规范维护成本:写得好的规范需要时间精力,不比手写代码轻松。写得不好的规范,生成的代码质量可想而知。

AI的局限性:现在的AI还是可能生成有安全隐患的代码、性能不佳的实现,或者完全误解你的意图。你还是得是个有经验的开发者,能看出问题在哪。

团队适应成本:让团队接受“不改代码改规范”这种思维转变,不是一两天的事。很多人第一反应是:“我直接改代码不是更快?”

不过有意思的是,SDD某种程度上也让DDD的理念更容易落地

想想看:如果你的领域模型就是规范的一部分,那么AI生成的代码自然会遵循这个模型。这比手动确保几十个开发人员都正确理解领域模型,要可靠得多。


我们正处在一个转折点。SDD的出现,与其说是对TDD、BDD、DDD的否定,不如说是在AI时代对这些思想的延续和重新诠释

它用一种更彻底的方式,试图解决软件工程中那个永恒的问题:如何确保我们的意图被准确地实现

当你在代码仓库的specs/目录下,看到那份用精确语言描述的系统规范,而旁边的代码文件只是这份规范的“衍生品”时,也许会感受到一丝震撼。

技术演进的本质,是对抽象层次的不断向上推移。从机器码到汇编,到高级语言,到面向对象,到领域驱动,现在到规范驱动——每一步,我们都把更多“意图”直接表达出来,让机器去处理实现的细节。

这个趋势不会停止。也许有一天,我们会用更接近人类思考的方式“编写”软件,而今天这些DDD、SDD的讨论,也会成为技术史中一段有趣的注脚。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐