TL;DR

003e 本文核心观点:

  1. Gherkin的局限 — 自然语言描述的验收标准存在歧义,无法自动验证
  2. 形式化规格 — 用数学精确描述行为,实现真正的可执行规格
  3. AI桥梁 — LLM可以将自然语言Gherkin自动转换为形式化表达
  4. 闭环验证 — 规格生成代码,代码反验证规格,形成自洽系统

📋 本文结构

  1. Gherkin的天花板 — 为什么自然语言不够
  2. 形式化规格入门 — 用数学描述行为
  3. AI自动转换 — Gherkin → 形式化规格 → 代码
  4. 闭环验证系统 — 规格与代码的双向约束
  5. 工具链实践 — 可落地的工程方案
  6. 结论 — 行为驱动的新纪元

Gherkin的天花板

💡 Key Insight

003e Gherkin让非技术人员能读验收标准,但”能读”不等于”能精确执行”。自然语言的模糊性是系统Bug的重要来源。

经典Gherkin示例

Feature: 订单折扣计算
  Scenario: VIP客户享受折扣
    Given 一个VIP客户
    When 他下单购买$100的商品
    Then 他应该支付$90

看起来清晰,但魔鬼在细节:

模糊点 可能解释A 可能解释B 可能解释C
“VIP客户” 积分≥1000 购买过3次以上 订阅了会员服务
“$100的商品” 原价$100 折扣前$100 一件商品$100
“支付$90” 最终金额$90 折扣$10 还需加税
“VIP” 终身VIP 当月VIP 特定等级VIP

这些歧义在实际开发中需要反复确认,每次确认都是时间和认知成本。

为什么模糊性是问题

产品经理写的Gherkin
        ↓
开发者理解A版本
        ↓
测试人员理解B版本
        ↓
验收时发现不一致
        ↓
开会澄清(30分钟)
        ↓
修改实现(2小时)
        ↓
重新测试(1小时)

每次模糊点平均消耗3-4小时。


形式化规格入门

💡 Key Insight

003e 形式化规格不是给机器看的,而是给所有人一个无可争议的事实来源。数学是唯一的通用语言。

什么是形式化规格

用数学符号精确描述系统行为,消除所有自然语言歧义。

非形式化:

"订单金额大于$50免运费"

半形式化(结构化):

condition: order.total_amount > 50
action: shipping_fee = 0

完全形式化(数学表达式):

∀ order ∈ Orders:
  order.total_amount > 50 → order.shipping_fee = 0

常用形式化方法

方法 适用场景 示例
谓词逻辑 状态约束 ∀x (P(x) → Q(x))
时序逻辑 事件顺序 ◇(A ∧ ○B) 最终A且接下来B
状态机 状态转换 State × Event → State
代数规格 数据结构 push(pop(stack)) = stack
合约式 前置/后置条件 requires: x > 0; ensures: result > 0

TLA+示例:简单规格

(* 订单状态机规格 *)
MODULE Order

EXTENDS Integers, Sequences, FiniteSets

CONSTANTS Items, Customers

VARIABLES orders, orderCounter

(* 类型不变式 *)
TypeInvariant ==
  orders ⊆ [id: Nat, 
            customer: Customers, 
            items: SUBSET Items,
            status: {"created", "paid", "shipped", "delivered", "cancelled"}]

(* 初始状态 *)
Init ==
  orders = {}
  orderCounter = 0

(* 创建订单 *)
CreateOrder(c, i) ==
  orderCounter' = orderCounter + 1
  orders' = orders ∪ {[id ↦ orderCounter',
                        customer ↦ c,
                        items ↦ i,
                        status ↦ "created"]}

(* 支付订单 *)
PayOrder(o) ==
  o ∈ orders
  o.status = "created"
  orders' = (orders \\ {o}) ∪ 
            {[id ↦ o.id,
              customer ↦ o.customer,
              items ↦ o.items,
              status ↦ "paid"]}
  UNCHANGED orderCounter

(* 状态转换 *)
Next ==
  ∃ c ∈ Customers, i ∈ SUBSET Items: CreateOrder(c, i)
  ∨ ∃ o ∈ orders: PayOrder(o)

(* 系统规格 *)
Spec == Init ∧ □[Next]_⟨orders, orderCounter⟩

这个规格可以被TLC模型检查器验证,确保没有死锁、状态不一致等问题。


AI自动转换

💡 Key Insight

003e LLM的强项是理解自然语言,形式化方法的强项是精确表达。两者结合:LLM将Gherkin翻译为形式化规格,再翻译为可执行代码。

转换Pipeline

┌─────────────────────────────────────────┐
│  输入: Gherkin自然语言描述               │
│                                         │
│  Given 一个VIP客户                      │
│  When 他下单购买$100的商品              │
│  Then 他应该支付$90                     │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│  步骤1: 歧义识别与澄清                   │
│                                         │
│  AI检测: "VIP客户"定义不明确             │
│  询问: VIP的判断标准是什么?             │
│  回答: 积分≥1000                        │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│  步骤2: 转换为结构化规格                 │
│                                         │
│  preconditions:                         │
│    - customer.points >= 1000            │
│    - order.original_amount == 100        │
│    - discount_rate = 0.1                 │
│  postconditions:                        │
│    - order.final_amount == 90            │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│  步骤3: 生成形式化表达式                 │
│                                         │
│  ∀ c ∈ Customer, o ∈ Order:             │
│    c.points ≥ 1000 ∧ o.amount = 100     │
│    → o.final = 90                        │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│  步骤4: 生成可执行代码+测试              │
│                                         │
│  def calculate_total(customer, order):  │
│      if customer.points >= 1000:        │
│          return order.amount * 0.9       │
│      return order.amount                 │
└─────────────────────────────────────────┘

实战:转换示例

输入Gherkin:

Scenario: 并发订单不超卖
  Given 商品A库存为10
  When 100个用户同时下单购买商品A
  Then 最多只有10个订单能成功
  And 所有成功的订单加起来不能超过10件

AI转换过程:

步骤1: 识别关键概念
- 库存: inventory[product_id] = quantity
- 订单: order[order_id] = {product, quantity, status}
- 并发: 多个操作同时发生

步骤2: 识别约束条件
- 全局不变式: Σ successful_orders.quantity ≤ inventory
- 原子性: 库存检查与扣减必须一起完成

步骤3: 形式化规格(TLA+)
```tla
(* 库存不变式 *)
InventoryInvariant ==
  ∀ p ∈ Products:
    LET sold = Sum({o.quantity : o ∈ orders ∧ o.product = p ∧ o.status = "success"})
    IN sold ≤ inventory[p]

(* 并发操作的原子性 *)
PlaceOrder(c, p, q) ==
  LET available = inventory[p] - Sum({o.quantity : o ∈ orders ∧ o.product = p ∧ o.status = "success"})
  IN IF available >= q
     THEN orders' = orders ∪ {[customer ↦ c, product ↦ p, quantity ↦ q, status ↦ "success"]}
     ELSE orders' = orders ∪ {[customer ↦ c, product ↦ p, quantity ↦ q, status ↦ "failed"]}

步骤4: 生成实现代码

@transaction.atomic
def place_order(customer_id, product_id, quantity):
    # SELECT FOR UPDATE 保证原子性
    product = Product.objects.select_for_update().get(id=product_id)
    
    sold = Order.objects.filter(
        product=product, 
        status='success'
    ).aggregate(Sum('quantity'))['quantity__sum'] or 0
    
    available = product.inventory - sold
    
    if available >= quantity:
        order = Order.objects.create(
            customer_id=customer_id,
            product=product,
            quantity=quantity,
            status='success'
        )
        return order
    else:
        Order.objects.create(
            customer_id=customer_id,
            product=product,
            quantity=quantity,
            status='failed'
        )
        raise InsufficientInventory()

步骤5: 生成验证测试

def test_concurrent_orders_no_oversell():
    # 设置库存
    product = Product.objects.create(inventory=10)
    
    # 并发执行100个订单请求
    with ThreadPoolExecutor(max_workers=100) as executor:
        futures = [
            executor.submit(place_order, i, product.id, 1)
            for i in range(100)
        ]
        results = [f.result() for f in futures]
    
    # 验证
    successful = [r for r in results if r.status == 'success']
    total_sold = sum(o.quantity for o in successful)
    
    assert len(successful) <= 10
    assert total_sold <= 10

---

## 闭环验证系统

> 💡 **Key Insight**
> 
003e 规格与代码应该是双向约束的:规格生成代码,代码反验证规格。任何不一致都说明理解偏差。

### 双向验证模型

     自然语言需求
           ↓
┌─────────────────────┐
│   AI转换引擎         │
│  (Gherkin → 形式化)  │
└─────────────────────┘
           ↓
   形式化规格
     ↙       ↘
    ↓         ↓    代码生成器   模型检查器
    ↓         ↓    可执行代码   性质验证报告
    ↓         ↓
┌─────────────────────┐
│   运行时监控         │
│  (检测规格违反)      │
└─────────────────────┘
           ↓
     偏差报告 → 反馈给规格 ```

运行时验证

# 规格作为装饰器
@invariant("balance >= 0")
@requires("amount > 0")
@ensures("balance == old(balance) - amount")
def withdraw(account, amount):
    account.balance -= amount
    return account.balance

# 运行时自动检查
class Account:
    def __init__(self):
        self.balance = 100
    
    @precondition(lambda self, amount: amount > 0)
    @postcondition(lambda result, self, amount: self.balance >= 0)
    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFunds()
        self.balance -= amount
        return self.balance

工具链实践

💡 Key Insight

003e 形式化方法不是银弹,但结合AI后,它可以成为日常工程工具。关键是选择合适的工具级别。

工具选择矩阵

复杂度 工具 学习成本 适用场景
Dafny/Contracts 1周 单个函数验证
Alloy 2-4周 数据结构/状态机
TLA+ 1-3月 分布式系统
超高 Coq/Isabelle 数月 安全关键系统

AI增强BDD工具链

┌─────────────────────────────────────────┐
│  Feature文件 (Gherkin)                  │
│  自然语言描述业务场景                    │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│  SpecAI (LLM转换层)                     │
│  - 歧义检测                             │
│  - 结构化转换                           │
│  - 形式化生成                           │
└─────────────────────────────────────────┘
                    ↓
          ┌─────────┴─────────┐
          ↓                   ↓
┌─────────────────┐   ┌─────────────────┐
│  形式化规格      │   │  可执行测试      │
│  - TLA+/Alloy   │   │  - 单元测试      │
│  - 模型检查     │   │  - 集成测试      │
└─────────────────┘   └─────────────────┘
          ↓                   ↓
┌─────────────────┐   ┌─────────────────┐
│  性质验证报告    │   │  测试报告        │
│  - 死锁检测     │   │  - 覆盖率        │
│  - 不变式检查   │   │  - 边界条件      │
└─────────────────┘   └─────────────────┘

渐进式采用策略

阶段1:增强Gherkin(1-2周)

  • 使用结构化Given-When-Then
  • 添加显式约束注释
  • AI辅助歧义检测

阶段2:轻量形式化(1-2月)

  • 关键业务流程使用合约式编程
  • 生成运行时断言
  • 边界条件自动枚举

阶段3:模型检查(3-6月)

  • 核心状态机使用TLA+
  • 并发场景模型验证
  • 规格-代码一致性检查

结论

🎯 Takeaway

传统BDD AI增强BDD
Gherkin是终点 Gherkin是起点
自然语言描述 自然语言 → 形式化规格 → 代码
人工验证 自动模型检查+运行时验证
测试与规格分离 规格即测试,测试即规格
歧义靠沟通解决 歧义靠形式化消除

BDD的初衷是让业务、开发、测试三方对齐。形式化规格让这种对齐可验证、可自动化

AI不是取代BDD,而是让BDD从”行为驱动开发”进化为”行为驱动工程“——从想法到可验证的实现,端到端自动化。

“规格越精确,实现越自由。形式化不是束缚,而是给创造力明确的边界。”


📚 延伸阅读

经典案例

  • AWS使用TLA+验证S3和DynamoDB核心算法:形式化方法在大规模系统的实践
  • Microsoft的Dafny项目:合约式编程的生产环境应用

本系列相关

学术理论

  • 《Specifying Systems》(Leslie Lamport): TLA+权威教材
  • 《Software Abstractions》(Daniel Jackson): Alloy方法指南
  • 《The B-Method》(Abrial): 形式化方法经典
  • 《Design by Contract》(Bertrand Meyer): 合约式设计奠基论文

AI-Native软件工程系列 #4 深度阅读时间:约 14 分钟