← Back to list

enforce-contract
by knowlet
A highly sophisticated, theoretically sound adaptation of Problem Frames for modern Agentic Software Engineering.
⭐ 2🍴 0📅 Jan 18, 2026
SKILL.md
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 標註
/**
* 建立新訂單
*
* @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 幻覺:
- 明確邊界:AI 必須先定義什麼是有效輸入
- 強制思考:AI 必須考慮異常情況
- 可驗證性:契約可以被測試驗證
- 自我約束:AI 生成的代碼有明確的行為規範
契約完整度 ∝ 1 / AI 幻覺發生率
Score
Total Score
65/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
✓説明文
100文字以上の説明がある
+10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
1ヶ月以内に更新
+10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon
