enforce-contract
8
总安装量
6
周安装量
#35046
全站排名
安装命令
npx skills add https://github.com/knowlet/skills --skill enforce-contract
Agent 安装分布
claude-code
4
gemini-cli
3
antigravity
3
codex
3
opencode
3
Skill 文档
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 幻覺ç¼çç