---
name: enforce-contract
description: 單元測試與代碼提交前觸發。掃描並驗證方法的 pre-conditions、post-conditions 與 invariants，透過契約式設計減少 AI 幻覺。
---

# Enforce Contract Skill

## 觸發時機

- 編寫單元測試前
- 實作 `analyze-frame` 產出的規格時
- 代碼提交（commit）前
- 實作新方法時
- AI 生成代碼後的驗證

## 核心任務

透過 Design by Contract 明確定義每個方法的邊界條件，極大化減少 AI 幻覺。

## 契約式設計三要素

### 1. Pre-conditions（前置條件）
- **定義**：呼叫方法前必須滿足的條件
- **責任歸屬**：呼叫者 (Caller) 的責任
- **違反時**：方法可以拒絕執行

### 2. Post-conditions（後置條件）
- **定義**：方法執行完畢後保證成立的條件
- **責任歸屬**：被呼叫者 (Callee) 的責任
- **違反時**：表示方法實作有 bug

### 3. Invariants（不變量）
- **定義**：物件生命週期內始終成立的條件
- **適用時機**：任何公開方法呼叫前後
- **違反時**：表示物件狀態已損壞

## 契約標註格式

### 使用 Javadoc 標註

```java
/**
 * 建立新訂單
 * 
 * @param input 建立訂單的輸入參數
 * @return 建立成功的訂單資訊
 * 
 * @pre input != null
 * @pre input.getCustomerId() != null
 * @pre input.getItems() != null && !input.getItems().isEmpty()
 * @pre 所有 items 的 quantity > 0
 * @pre 所有 items 的 productId 對應的商品存在
 * 
 * @post result != null
 * @post result.getOrderId() != null
 * @post result.getStatus() == OrderStatus.CREATED
 * @post 訂單已持久化到資料庫
 * @post OrderCreatedEvent 已發布
 * 
 * @throws CustomerNotFoundException 當 customerId 對應的客戶不存在
 * @throws ProductNotFoundException 當 productId 對應的商品不存在
 * @throws InsufficientInventoryException 當庫存不足
 */
public Output execute(Input input) {
    // 實作
}
```

### 使用程式碼驗證 Pre-conditions

```java
public Output execute(Input input) {
    // ===== Pre-conditions =====
    Objects.requireNonNull(input, "input must not be null");
    Objects.requireNonNull(input.getCustomerId(), "customerId must not be null");
    
    if (input.getItems() == null || input.getItems().isEmpty()) {
        throw new IllegalArgumentException("items must not be empty");
    }
    
    for (OrderItemRequest item : input.getItems()) {
        if (item.getQuantity() <= 0) {
            throw new IllegalArgumentException(
                "quantity must be positive, got: " + item.getQuantity()
            );
        }
    }
    
    // ===== 主要邏輯 =====
    // ...
    
    // ===== Post-conditions (assert in development) =====
    assert result != null : "result must not be null";
    assert result.getOrderId() != null : "orderId must not be null";
    
    return result;
}
```

## Entity/Aggregate 的 Invariants

### 範例：Order Aggregate

```java
public class Order {
    private OrderId id;
    private CustomerId customerId;
    private List<OrderItem> items;
    private OrderStatus status;
    private Money totalAmount;
    
    /**
     * Order 的不變量：
     * @invariant id != null
     * @invariant customerId != null
     * @invariant items != null && !items.isEmpty()
     * @invariant totalAmount != null && totalAmount.isPositive()
     * @invariant status != null
     * @invariant 當 status == CANCELLED 時，不能再修改訂單內容
     */
    
    // 建構子必須建立有效狀態
    public Order(OrderId id, CustomerId customerId, List<OrderItem> items) {
        // Pre-conditions
        Objects.requireNonNull(id, "id must not be null");
        Objects.requireNonNull(customerId, "customerId must not be null");
        if (items == null || items.isEmpty()) {
            throw new IllegalArgumentException("items must not be empty");
        }
        
        this.id = id;
        this.customerId = customerId;
        this.items = new ArrayList<>(items);
        this.status = OrderStatus.CREATED;
        this.totalAmount = calculateTotal();
        
        // 驗證 invariants
        assertInvariants();
    }
    
    public void addItem(OrderItem item) {
        // Pre-conditions
        Objects.requireNonNull(item, "item must not be null");
        if (this.status == OrderStatus.CANCELLED) {
            throw new IllegalStateException("Cannot modify cancelled order");
        }
        
        // 執行變更
        this.items.add(item);
        this.totalAmount = calculateTotal();
        
        // Post-conditions & Invariants
        assertInvariants();
    }
    
    public void cancel() {
        // Pre-conditions
        if (this.status == OrderStatus.SHIPPED) {
            throw new IllegalStateException("Cannot cancel shipped order");
        }
        
        // 執行變更
        this.status = OrderStatus.CANCELLED;
        
        // Invariants
        assertInvariants();
    }
    
    private void assertInvariants() {
        assert id != null : "Invariant violated: id is null";
        assert customerId != null : "Invariant violated: customerId is null";
        assert items != null && !items.isEmpty() : "Invariant violated: items is empty";
        assert totalAmount != null && totalAmount.isPositive() : 
            "Invariant violated: totalAmount is invalid";
        assert status != null : "Invariant violated: status is null";
    }
}
```

## 契約掃描檢查項目

### 必須檢查的項目

| 項目 | 描述 | 嚴重度 |
|------|------|--------|
| Null Check | 所有物件參數是否有 null 檢查 | 🔴 嚴重 |
| Empty Collection | 集合參數是否檢查 empty | 🟡 中度 |
| Positive Numbers | 數量、金額等是否檢查正數 | 🟡 中度 |
| Valid State | 狀態轉換是否合法 | 🔴 嚴重 |
| Return Value | 回傳值是否可能為 null | 🟡 中度 |

### 掃描規則

```yaml
contract_rules:
  pre_conditions:
    - rule: null_check_for_objects
      description: "物件型別參數必須有 null 檢查"
      pattern: "public.*\\(.*[A-Z]\\w+\\s+\\w+"
      check: "Objects.requireNonNull|!= null"
      
    - rule: empty_check_for_collections
      description: "集合型別必須檢查是否為空"
      applies_to: ["List", "Set", "Collection"]
      check: "isEmpty()|!.*\\.isEmpty()"
      
    - rule: positive_check_for_quantities
      description: "數量類型必須檢查大於零"
      applies_to: ["quantity", "amount", "count", "size"]
      check: "> 0|>= 1|isPositive"

  post_conditions:
    - rule: non_null_return
      description: "標註 @NonNull 的回傳值必須確保不為 null"
      
    - rule: state_consistency
      description: "狀態變更後 invariants 必須成立"

  invariants:
    - rule: aggregate_validity
      description: "Aggregate 必須定義 assertInvariants() 方法"
      applies_to: "Aggregate"
```

## 與測試的整合

### 契約驅動測試

```java
class CreateOrderUseCaseTest {
    
    // ===== Pre-condition 測試 =====
    
    @Test
    @DisplayName("當 input 為 null 時，應拋出 NullPointerException")
    void should_throw_when_input_is_null() {
        // Given
        CreateOrderUseCase useCase = createUseCase();
        
        // When & Then
        assertThrows(NullPointerException.class, () -> {
            useCase.execute(null);
        });
    }
    
    @Test
    @DisplayName("當 items 為空時，應拋出 IllegalArgumentException")
    void should_throw_when_items_is_empty() {
        // Given
        Input input = new Input(customerId, Collections.emptyList(), address);
        
        // When & Then
        assertThrows(IllegalArgumentException.class, () -> {
            useCase.execute(input);
        });
    }
    
    // ===== Post-condition 測試 =====
    
    @Test
    @DisplayName("成功建立訂單後，應回傳有效的 OrderId")
    void should_return_valid_orderId_on_success() {
        // Given
        Input input = createValidInput();
        
        // When
        Output output = useCase.execute(input);
        
        // Then - 驗證 post-conditions
        assertNotNull(output);
        assertNotNull(output.getOrderId());
        assertEquals(OrderStatus.CREATED, output.getStatus());
    }
    
    @Test
    @DisplayName("成功建立訂單後，應發布 OrderCreatedEvent")
    void should_publish_event_on_success() {
        // Given
        Input input = createValidInput();
        
        // When
        useCase.execute(input);
        
        // Then - 驗證 post-condition
        verify(eventPublisher).publish(any(OrderCreatedEvent.class));
    }
}
```

## 檢查清單

### 實作新方法時

- [ ] 是否定義並記錄 pre-conditions？
- [ ] 是否在程式碼中驗證 pre-conditions？
- [ ] 是否定義 post-conditions？
- [ ] 是否有對應的測試案例？

### 實作 Entity/Aggregate 時

- [ ] 是否定義 invariants？
- [ ] 是否實作 assertInvariants() 方法？
- [ ] 建構子是否建立有效狀態？
- [ ] 所有公開方法是否維護 invariants？

### 代碼審查時

- [ ] pre-conditions 是否足夠嚴謹？
- [ ] 是否遺漏邊界條件？
- [ ] 錯誤訊息是否足夠清楚？
- [ ] 測試是否涵蓋所有契約？

## AI 幻覺預防

透過契約式設計，可以有效減少 AI 幻覺：

1. **明確邊界**：AI 必須先定義什麼是有效輸入
2. **強制思考**：AI 必須考慮異常情況
3. **可驗證性**：契約可以被測試驗證
4. **自我約束**：AI 生成的代碼有明確的行為規範

```
契約完整度 ∝ 1 / AI 幻覺發生率
```
