enforce-contract

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

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "enforce-contract" with this command: npx skills add knowlet/skills/knowlet-skills-enforce-contract

Enforce Contract Skill

觸發時機

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

核心任務

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

契約式設計三要素

1. Pre-conditions(前置條件)

  • 定義:呼叫方法前必須滿足的條件
  • 責任歸屬:呼叫者 (Caller) 的責任
  • 違反時:方法可以拒絕執行

2. Post-conditions(後置條件)

  • 定義:方法執行完畢後保證成立的條件
  • 責任歸屬:被呼叫者 (Callee) 的責任
  • 違反時:表示方法實作有 bug

3. Invariants(不變量)

  • 定義:物件生命週期內始終成立的條件
  • 適用時機:任何公開方法呼叫前後
  • 違反時:表示物件狀態已損壞

契約標註格式

使用 Javadoc 標註

/**
 * 建立新訂單
 * 
 * @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

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

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🟡 中度

掃描規則

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"

與測試的整合

契約驅動測試

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 幻覺發生率

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

spec-compliance-validator

No summary provided by upstream source.

Repository SourceNeeds Review
General

multi-model-reviewer

No summary provided by upstream source.

Repository SourceNeeds Review
General

coding-standards

No summary provided by upstream source.

Repository SourceNeeds Review