当代码生成的成本趋近于零,真正稀缺的不再是"写代码的速度",而是"把问题说清楚的能力"。这篇文章试图用一个统一的框架,解释四个看似不相干的概念如何共同构成 AI 编程时代的底层认知结构。
引子:一个真实的翻车现场
去年我参与了一个 AI 辅助开发的内部项目。需求听起来很简单:"给系统加一个暗黑模式切换功能。"团队用 Cursor 配合 Claude,半天就生成了上千行代码------主题变量、切换组件、持久化逻辑、CSS 覆盖,一应俱全。代码能跑,UI 也能切换。
但上线后一周,问题开始爆发:
- 有人发现刷新页面后主题丢失,因为 AI 默认用
localStorage,而我们的系统有统一的用户偏好存储服务。 - 有人发现部分页面的图表颜色没跟着切换,因为 AI 只改了 CSS 变量,没有处理 Canvas 绘制的图表。
- 有人发现打印预览时背景是黑的,因为 AI 没有考虑打印样式。
- 更严重的是,主题状态散落在三个不同的 Context 里,互相不同步。
问题不在于 AI 不会写代码。问题在于我们没有把"暗黑模式"这个概念的边界、约束和验收标准讲清楚。我们给了 AI 一个模糊的意图,它给了我们一堆看起来正确的代码。
这个经历让我重新思考四个经常被混为一谈的概念:本体论、DDD、SDD、TDD。它们分别对应着软件构建过程中四个不同层级的认知问题。理解它们之间的关系,可能比学会任何一个具体框架都重要。
一、本体论:DDD 沉默的哲学底座
1.1 什么是本体论
本体论(Ontology)是哲学的一个分支,研究"存在本身"------什么东西可以被认为是存在的,它们如何分类,如何关联。听起来很抽象,但它在软件工程中无处不在。
当你写下这样的代码:
java
public class Order {
private Long id;
private BigDecimal amount;
private OrderStatus status;
}
你其实已经做出了一系列本体论承诺:
- "订单"是一个独立存在的实体,它有唯一的标识(id),即使它的金额和状态都变了,它仍然是同一个订单。
- "金额"是一个值,它没有独立身份,只是订单的一个属性。
- "订单状态"是一个枚举,它定义了订单可能存在的几种"存在方式"。
这些承诺决定了你如何切割业务世界。如果你把"库存"建模成一个简单的 int,那么你就默认了库存只是一个数字。但真实的库存有预留、锁定、在途、可用、过期等多种状态。当业务需求变化时,这个简单的 int 会成为架构的噩梦。
1.2 DDD 的隐式本体论
Eric Evans 在《领域驱动设计》中提出的实体(Entity)和值对象(Value Object)的区分,本质上就是一次本体论选择:
| 概念 | 本体论承诺 | 例子 |
|---|---|---|
| 实体 | 有持续同一性,靠 ID 识别 | 用户、订单、商品 |
| 值对象 | 无唯一标识,靠属性值定义 | 地址、金额、时间段 |
| 聚合 | 一组相关对象的边界,保证一致性 | 订单 + 订单行 |
| 领域事件 | 领域中发生的、有意义的事情 | 订单已提交、支付已完成 |
DDD 继承了面向对象的实体本体观------对象是建模的中心,关系靠对象引用表达,行为是对象的方法。但这个本体论是隐式的。它把这些承诺埋在代码里,靠开发者的纪律去维持。
1.3 局部本体论 vs 全局本体论
一个常见的误区是追求"唯一的、客观的全局本体"。在微服务架构中,同一个"订单"在不同限界上下文里有截然不同的定义:
- 销售上下文:订单关注价格、折扣、促销、客户信用。
- 履约上下文:订单关注库存、仓库、物流、配送时效。
- 财务上下文:订单关注发票、税额、收入确认、退款。
- 客服上下文:订单关注售后状态、投诉记录、退换货。
如果你强行要求所有上下文使用同一个 Order 类,结果要么是一个臃肿的"上帝类",要么是无数个 if (context == SALES) 的分支。
正确的做法是承认局部本体论的合法性。 每个限界上下文内部可以有一套自洽的概念体系,跨上下文时则需要显式的"本体映射"------谁在什么条件下把"订单"翻译成什么。这个映射不应该藏在某个 Service 的 if 分支里,而应该成为架构中一等公民。
1.4 一个本体论错误的代价
假设你在做一个网约车系统。最初的建模是这样的:
java
public class Trip {
private Long id;
private Long passengerId;
private Long driverId;
private Double startLat;
private Double startLng;
private Double endLat;
private Double endLng;
private TripStatus status;
}
看起来没问题。但业务发展后,出现了"拼车"需求------一个行程可能有多个乘客,每个乘客有各自的上下车点。你试图在 Trip 里加一个 List<Passenger>,但发现司机接单逻辑、计费逻辑、调度逻辑全都假设了一个行程只有一个乘客。
根本问题在于:你把"行程"和"乘客的乘车需求"混为一谈了。 在本体论层面,应该存在两个不同的实体:
Trip:司机的一段连续驾驶过程,有起点和终点。RideRequest:一个乘客从 A 到 B 的出行需求,可以被匹配到一个 Trip 上。
拼车场景下,一个 Trip 可以承载多个 RideRequest。这个本体论修正会波及整个系统的设计,但如果你一开始就做对了,后续的扩展会自然得多。
1.5 Palantir Foundry 的启示
Palantir 的 Foundry 平台把本体论工程化推向了极致。它的 Ontology 层将业务对象、属性和关系从代码中抽离出来,变成平台级的语义基础设施:
- ObjectType:定义业务对象(如"供应商"、"订单"、"设备")。
- LinkType:定义对象之间的关系(如"供应商供货给订单")。
- ActionType:定义可以对对象执行的操作(如"审批订单"、"调整库存")。
这些构件本质上是把 DDD 里的"实体、关系、领域服务"从代码约定升级为可被机器读取和校验的显式结构。AI 能理解这些结构,因为它能"看到"规则本身,而不仅仅是规则在代码中的痕迹。
这给我们的启示是:本体论不应该只是开发者脑子里的默契,而应该成为可共享、可校验、可演进的显式资产。
二、DDD:一场"靠纪律维持的建模运动"
2.1 DDD 的核心主张
DDD 在 2003 年由 Eric Evans 系统化提出,核心主张是:软件应该围绕领域模型构建,而不是围绕技术分层。
战略设计层面,它用限界上下文(Bounded Context)拆分复杂业务,用上下文映射(Context Map)定义上下文之间的关系。
战术设计层面,它用实体、值对象、聚合根、领域事件、仓储、领域服务等构件,把业务规则编码进对象结构里。
2.2 一个 DDD 做得好的例子:保险理赔系统
假设我们在做一个保险理赔系统。没有 DDD 的时候,代码可能是这样的:
java
public class ClaimService {
public void processClaim(Long claimId) {
Claim claim = claimRepo.findById(claimId);
if (claim.getStatus() == 1) {
// 一堆 if-else
if (claim.getAmount() > 10000) {
// 需要人工审核
} else {
// 自动审核
}
}
// 更多逻辑...
}
}
业务规则散落在 Service 的 if-else 里,状态流转靠魔法数字,没有任何地方能告诉你"一个理赔单从提交到结案,合法的状态路径是什么"。
用 DDD 重构后:
java
// 聚合根
public class Claim {
private ClaimId id;
private ClaimStatus status;
private Money claimedAmount;
private List<ClaimItem> items;
// 业务行为
public void submit() {
if (status != ClaimStatus.DRAFT) {
throw new IllegalStateException("只有草稿状态可以提交");
}
this.status = ClaimStatus.SUBMITTED;
// 发布领域事件
registerEvent(new ClaimSubmittedEvent(this.id));
}
public void approve(ApprovalDecision decision) {
if (status != ClaimStatus.SUBMITTED) {
throw new IllegalStateException("只有已提交状态可以审批");
}
if (decision.requiresManualReview() && !decision.hasReviewer()) {
throw new IllegalArgumentException("人工审批必须有审核人");
}
this.status = ClaimStatus.APPROVED;
registerEvent(new ClaimApprovedEvent(this.id, decision));
}
public void reject(String reason) {
if (status != ClaimStatus.SUBMITTED) {
throw new IllegalStateException("只有已提交状态可以拒绝");
}
this.status = ClaimStatus.REJECTED;
registerEvent(new ClaimRejectedEvent(this.id, reason));
}
}
现在,业务规则被编码进聚合根的行为里。状态流转有明确的路径,非法操作会直接抛异常。领域事件让其他上下文可以订阅理赔的状态变化。
2.3 DDD 的困境:纪律的脆弱性
DDD 的理念在方向上无可指摘。问题出在实现机制上。
DDD 试图通过"约定和纪律"维持模型一致性,但没有提供将规则显式化、工具化的手段。聚合边界、业务规则、状态约束,最终都落回到代码里。文本化的代码不是表达这些内容的好载体------Lint 等静态检查工具无法自动化检查"订单金额超过 10 万需要总监审批"这条规则是否在重构中丢失了。
更根本的问题是协作成本。统一语言(Ubiquitous Language)需要业务专家与开发者长期协作维护,但在交付压力下,这种纪律几乎无法坚持。业务用一套词,IT 用另一套概念,翻译过程中核心语义悄然流失。几轮迭代后,业务规则重新回到代码和 SQL 里,领域语义再次变得分散隐性。
我见过太多项目,DDD 的"统一语言"最终变成了一份无人维护的术语表,躺在 Confluence 的角落里积灰。
DDD 的困境不是方向错了,而是在没有平台支撑的情况下,维持它所需的多方持续投入太难了。 它把"语义显式化"的任务交给了人的纪律,而人的纪律在时间压力下是最不可靠的东西。
三、SDD:当"代码服务规格"
3.1 什么是 SDD
SDD(Spec-Driven Development,规格驱动开发)是这四个概念中最年轻的一个,但它的范式反转最为激进。
传统开发中,代码是真相来源,规格文档是脚手架,一旦"真正的编码"开始,文档就被丢弃。SDD 颠倒了这个权力结构:规格不服务代码,代码服务规格。
产品需求文档不是实施指南,而是产生实现的来源;技术方案不是指导编码的文件,而是产生代码的精确定义。
这个反转之所以在 2025 年前后迅速获得关注,直接原因是 AI 编程的兴起。当你让 AI"实现暗黑模式"时,它可能默认主题状态放在 localStorage,可能默认所有页面都要改,可能为了实现新功能重构一堆公共样式。问题不在于 AI 不会写代码,而在于人没有把规格讲清楚。
3.2 一个 SDD 规格的例子
回到暗黑模式的例子。一个 SDD 风格的规格可能长这样:
markdown
# 暗黑模式切换功能规格
## 目标
用户可以在浅色、深色、跟随系统三种主题模式之间切换。
主题偏好需要跨设备同步。
## 行为定义
1. 用户点击主题切换按钮时,弹出三个选项:浅色、深色、跟随系统。
2. 选择后,当前页面主题立即生效,无需刷新。
3. 主题偏好保存到用户偏好服务(UserPreferenceService),
而不是 localStorage。
4. 用户下次登录时,从用户偏好服务读取主题设置。
5. 如果用户选择"跟随系统",则监听系统主题变化事件,
实时切换。
## 边界与约束
- 主题切换只影响 UI 层,不影响业务逻辑。
- 图表组件(ChartComponent)必须通过主题服务获取颜色,
不能硬编码颜色值。
- 打印样式始终使用浅色主题,不跟随用户设置。
- 主题状态必须集中管理,不允许在多个 Context 中散落。
## 验收标准
- [ ] 刷新页面后主题不丢失。
- [ ] 切换主题时,所有页面元素(包括 Canvas 图表)同步变化。
- [ ] 打印预览始终为浅色背景。
- [ ] 主题状态在 Redux DevTools 中只有一个来源。
- [ ] 单元测试覆盖三种模式的切换逻辑。
- [ ] 集成测试验证跨设备同步。
这份规格不是"需求文档",它是可执行的意图。AI 可以基于它生成代码,测试可以基于它编写用例,Code Review 可以基于它检查实现是否偏离。
3.3 SDD 与 DDD 的关系
SDD 的深层意义在于,它把软件的"核心资产"从代码转移到了规格。当代码可以按需重新生成时,值得长期维护的就不再是某一份具体实现,而是那份能产生正确实现的规格。
这听起来离 DDD 并不远。DDD 追求的是让代码忠实反映业务意图,SDD 追求的是让代码忠实反映规格意图。区别在于,DDD 的意图藏在开发者的纪律里,SDD 的意图是可执行、可校验的显式产物。
SDD 可以看作是 DDD"统一语言"理想的一种工程化实现路径------不是靠人记住术语表,而是靠规格本身约束实现。但 SDD 不能替代 DDD。没有 DDD 提供的领域边界和概念定义,SDD 容易退化为另一种"氛围编码":规格看起来很详细,但概念底层是混乱的。
DDD 回答"领域里有哪些概念、边界在哪里",SDD 回答"这些概念如何转化为可执行的意图"。 两者是上下游关系,不是竞争关系。
四、TDD:质量契约的不可替代性
4.1 TDD 的争议
TDD 的争议从它诞生那天起就没停过。反对者的核心论点很扎实:Test First 隐含了一个假设------需求和设计的细节可以在实现之前明确下来。但在实际项目中,用户需求的不确定性意味着很多细节是在"真正能运行看到效果"之后才逐步清晰的。要求先写精确的测试用例,等于要求先知道终点在哪里。
这个批评是成立的,但它没有否定 TDD 的全部价值。TDD 真正的贡献不在于"先写测试"这个动作顺序,而在于它确立了一条原则:每一段实现代码,都应该有一个明确的、可自动验证的预期行为作为锚点。
4.2 一个 TDD 的例子:价格计算器
假设我们要实现一个电商的价格计算器,规则如下:
- 商品原价 100 元。
- VIP 用户享受 9 折。
- 满 200 元减 20 元。
- 优惠券可以叠加,但最终价格不能低于 0。
用 TDD 的方式,我们先写测试:
java
@Test
public void testNormalUserNoDiscount() {
PriceCalculator calculator = new PriceCalculator();
Money result = calculator.calculate(
new Money(100),
UserType.NORMAL,
Collections.emptyList()
);
assertEquals(new Money(100), result);
}
@Test
public void testVipUserGet10PercentOff() {
PriceCalculator calculator = new PriceCalculator();
Money result = calculator.calculate(
new Money(100),
UserType.VIP,
Collections.emptyList()
);
assertEquals(new Money(90), result);
}
@Test
public void testFull200Minus20() {
PriceCalculator calculator = new PriceCalculator();
Money result = calculator.calculate(
new Money(250),
UserType.NORMAL,
Collections.emptyList()
);
assertEquals(new Money(230), result);
}
@Test
public void testVipAndCouponStack() {
PriceCalculator calculator = new PriceCalculator();
Money result = calculator.calculate(
new Money(250),
UserType.VIP,
List.of(new Coupon(30))
);
// 250 * 0.9 = 225, 满 200 减 20 → 205, 205 - 30 = 175
assertEquals(new Money(175), result);
}
@Test
public void testPriceNeverBelowZero() {
PriceCalculator calculator = new PriceCalculator();
Money result = calculator.calculate(
new Money(50),
UserType.NORMAL,
List.of(new Coupon(100))
);
assertEquals(new Money(0), result);
}
这些测试先于实现存在。它们定义了"正确"的含义。实现代码的任务就是让这些测试通过。红-绿-重构的循环,本质上是在用最小的可验证单元来约束代码的演化方向。
4.3 TDD 在 AI 编程中的新角色
在 AI 编程的语境下,TDD 的角色反而变得更清晰了。AI 可以快速生成大量代码,但"快速生成正确代码"和"快速生成看起来正确的代码"之间的差距,只能靠验证来弥合。
SDD 定义了"应该做什么",TDD 验证了"做出来的东西是否符合定义"。两者不是竞争关系,而是规格与验收的天然配对:
- SDD 负责定义正确的行为。
- TDD 负责验证行为的正确。
当 SDD 的规格足够精确时,TDD 的测试很大程度上可以从规格中直接推导出来。比如规格里写"最终价格不能低于 0",对应的测试就是 testPriceNeverBelowZero。规格里写"VIP 用户享受 9 折",对应的测试就是 testVipUserGet10PercentOff。
没有 TDD 的 SDD,规格只是愿望;没有 SDD 的 TDD,测试只是碎片。 两者结合,才能形成从意图到验证的完整闭环。
五、四层认知栈的整合视角
5.1 四层结构总览
把这四个概念放在一个纵向结构里看,它们各自占据了一个不可替代的位置:
| 层级 | 核心问题 | 回答者 | 产出物 | 失败模式 |
|---|---|---|---|---|
| 本体论层 | 什么存在? | 领域专家 + 架构师 | 概念模型、术语表、本体定义 | 概念混乱、边界模糊 |
| DDD 层 | 如何把存在结构化? | 领域专家 + 开发者 | 限界上下文、聚合、领域事件 | 模型与业务脱节、语义债务 |
| SDD 层 | 如何把结构转化为可执行意图? | 产品 + 开发者 | 规格文档、验收标准 | 规格模糊、AI 自由发挥 |
| TDD 层 | 如何验证实现与意图一致? | 开发者 | 测试用例、自动化验证 | 回归风险、质量失控 |
5.2 一个贯穿四层的完整例子:网约车系统
让我们用一个网约车系统的例子,展示四层如何协同工作。
本体论层决策:
- "行程(Trip)"是司机的一段连续驾驶过程。
- "出行需求(RideRequest)"是乘客从 A 到 B 的需求。
- "匹配(Match)"是 Trip 和 RideRequest 之间的关联关系。
- "计费(Fare)"是基于 Trip 的实际路径和时长计算出的金额。
这个本体论选择决定了后续所有建模的方向。如果错误地把 Trip 和 RideRequest 合并成一个实体,拼车场景就会变得极其别扭。
DDD 层设计:
java
// 聚合根:Trip
public class Trip {
private TripId id;
private DriverId driverId;
private List<Match> matches;
private TripStatus status;
public void start() {
if (status != TripStatus.DRIVER_EN_ROUTE) {
throw new IllegalStateException("司机未在前往接驾状态");
}
this.status = TripStatus.IN_PROGRESS;
registerEvent(new TripStartedEvent(this.id));
}
public void complete(Location endLocation, Duration duration) {
if (status != TripStatus.IN_PROGRESS) {
throw new IllegalStateException("行程未在进行中");
}
this.status = TripStatus.COMPLETED;
registerEvent(new TripCompletedEvent(this.id, endLocation, duration));
}
// 匹配一个出行需求(需校验剩余座位数、行程是否已开始等)
public void match(RideRequest request) {
if (status != TripStatus.DRIVER_EN_ROUTE) {
throw new IllegalStateException("行程未在可匹配状态");
}
if (remainingSeats() < 1) {
throw new NoSeatsAvailableException();
}
request.markMatched(this.id);
registerEvent(new RideRequestMatchedEvent(request.getId(), this.id));
}
}
// 实体:RideRequest
public class RideRequest {
private RideRequestId id;
private PassengerId passengerId;
private Location pickup;
private Location dropoff;
private RideRequestStatus status;
public void markMatched(TripId tripId) {
if (status != RideRequestStatus.PENDING) {
throw new IllegalStateException("需求已被匹配");
}
this.status = RideRequestStatus.MATCHED;
registerEvent(new RideRequestMatchedEvent(this.id, tripId));
}
}
SDD 层规格:
markdown
# 拼车匹配功能规格
## 目标
一个行程可以匹配多个出行需求,但总乘客数不能超过车辆座位数。
## 行为定义
1. 司机接单时,系统检查车辆剩余座位数。
2. 如果剩余座位数 >= 1,允许匹配新的 RideRequest。
3. 匹配后,RideRequest 状态变为 MATCHED。
4. 如果行程已经开始,不允许再匹配新的需求。
5. 乘客取消需求时,释放座位,状态变为 CANCELLED。
## 边界与约束
- 座位数计算不包括司机。
- 儿童座椅占用一个额外座位。
- 匹配算法需要考虑路线偏离不超过 15 分钟。
## 验收标准
- [ ] 匹配 3 个乘客后,第 4 个匹配请求被拒绝。
- [ ] 行程开始后,匹配请求被拒绝。
- [ ] 乘客取消后,座位被释放。
- [ ] 路线偏离超过 15 分钟时,匹配请求被拒绝。
TDD 层测试:
java
@Test
public void testMatchWhenSeatsAvailable() {
Trip trip = new Trip(vehicleWithSeats(3));
RideRequest request = new RideRequest();
trip.match(request);
assertEquals(RideRequestStatus.MATCHED, request.getStatus());
}
@Test
public void testRejectMatchWhenNoSeats() {
Trip trip = new Trip(vehicleWithSeats(0));
RideRequest request = new RideRequest();
assertThrows(NoSeatsAvailableException.class, () -> {
trip.match(request);
});
}
@Test
public void testRejectMatchAfterTripStarted() {
Trip trip = new Trip(vehicleWithSeats(3));
trip.start();
RideRequest request = new RideRequest();
assertThrows(TripAlreadyStartedException.class, () -> {
trip.match(request);
});
}
5.3 上层决定下层,下层不会自动继承上层的正确性
这个四层结构的核心洞见是:上层决定下层,但下层不会自动继承上层的正确性。
- 一个本体论错误不可能被优秀的 TDD 修复。如果你把"库存"建模成一个简单的数字,再多的测试也只能保证这个数字的计算是正确的,无法解决"预留库存"和"可用库存"混淆的根本问题。
- 一套清晰的规格如果不基于正确的领域模型,只会更快地产生错误结果。规格写得越详细,基于错误模型的实现就越难纠正。
- 反过来,从下往上的反馈也是有价值的。TDD 暴露的边界条件可以反过来修正规格,规格中的歧义可以暴露本体论层面的概念模糊。
5.4 常见的反模式:跳过层级
在实际项目中,最常见的反模式是跳过上层,直接在下层发力:
- 跳过本体论,直接写代码:结果是概念混乱,同样的东西在不同地方叫不同名字,或者不同的东西叫同一个名字。
- 跳过 DDD,直接写规格:规格看起来很详细,但概念底层是混乱的,AI 生成的代码会在细节上正确,在结构上错误。
- 跳过 SDD,直接让 AI 写代码:这是"氛围编码"的典型场景,代码生成很快,但没人知道它是否满足需求。
- 跳过 TDD,直接上线:每次修改都伴随着不确定的回归风险,质量靠人肉保证。
你可以用轻量的方式回答这些问题,但不能跳过它们。 小团队可能不需要正式的限界上下文命名,但"这个字段到底代表什么意思"的困惑,在任何一个超过三人的代码库里都会出现。
六、AI 时代改变了什么
6.1 成本结构的反转
AI 时代真正改变的,不是这些问题的存在与否,而是回答它们的成本结构。
过去,写代码是昂贵的,所以人们倾向于在代码里快速试错。规格文档、领域模型、测试用例,这些"非代码"的工件被视为额外负担,能省则省。
现在,写代码变得廉价,而理解代码、验证代码、维护代码依然昂贵。当 AI 可以在几分钟内生成上千行代码时,真正稀缺的不再是"写代码的速度",而是"把问题说清楚的能力"。
这就是为什么 SDD 和 TDD 在 AI 编程语境下反而变得更重要的原因。它们是约束 AI 自由发挥的护栏。没有这些护栏,AI 生成代码的速度越快,项目失控的速度也越快。
6.2 本体论和 DDD 的价值回归
同样,本体论和 DDD 的价值也在回归。当代码可以按需重新生成时,值得长期维护的不再是某一份具体实现,而是概念模型和领域边界。
这就像建筑行业:施工可以外包,可以自动化,但建筑设计、结构计算、规范符合性,这些是不能外包的核心能力。软件工程正在经历类似的分工:编码可以外包给 AI,但建模、规格、验证,这些是工程师不可替代的核心能力。
6.3 一个可能的未来工作流
想象一下未来的开发工作流:
- 本体论工作坊:领域专家和架构师一起,定义核心概念、边界和关系,产出显式的本体定义。
- DDD 建模:基于本体定义,设计限界上下文、聚合、领域事件,产出领域模型。
- SDD 规格编写:基于领域模型,编写可执行的规格,定义行为、边界和验收标准。
- AI 生成实现:AI 基于规格生成代码,开发者 Review 和调整。
- TDD 验证:基于规格自动生成或手动编写测试,验证实现是否符合规格。
- 反馈循环:测试暴露的问题反馈到规格,规格中的歧义反馈到领域模型,领域模型的困惑反馈到本体定义。
这个工作流中,人的核心价值在于定义问题 ,而不是解决问题。定义问题需要本体论思考、领域建模、规格编写的能力;解决问题可以越来越多地交给 AI。
结语:各自的位置
在 AI 编程的讨论中,常见的一种简化是:SDD + TDD 就够了,DDD 和本体论是"重"的方法论,适合大团队,小团队不需要。这个判断忽略了一个事实------概念混乱的代价不因团队规模而消失,只是以不同的形式出现。
本体论、DDD、SDD、TDD,与其说是四套可以"选择使用"的方法,不如说是四个必须被在某个层级上做出决策的问题:
- 你用什么概念描述业务?
- 这些概念如何在模型中结构化?
- 结构化的意图如何变成可执行的规格?
- 规格如何被验证?
你可以用轻量的方式回答这些问题,但不能跳过它们。跳过本体论决策的代价,是在代码里积累语义债务;跳过 DDD 的代价,是模型与业务渐行渐远;跳过 SDD 的代价,是 AI 在模糊需求上自由发挥;跳过 TDD 的代价,是每一次修改都伴随着不确定的回归风险。
AI 时代真正改变的,不是这些问题的存在与否,而是回答它们的成本结构。当代码生成变得廉价,显式建模和规格定义的价值反而上升了------因为它们是唯一能让"快速生成"不变成"快速失控"的约束。
代码可以生成,但理解不能。 这可能是 AI 时代软件工程最核心的命题。