「有人立過誓,也有人毀過誓。夜色帶得走一句口頭的承諾,帶不走一行寫進帳本的字。」
——《阿帕契開源審計錄》¹ 卷一·契約篇
幕間
女巫終於決定用毒,倒向一個白天發言老是慢半拍的座位。
天亮,法官只報那個座位夜裡沒了人——沒有翻牌,沒有遺言,誰也不知道倒下的是狼還是好人。
「我到底毒對了沒有?」女巫在帷幕後發抖,這個問題她永遠得不到答案。獵人按住她:「別聲張,還有預言家。他昨晚驗的結果,寫下來了。」
城堡大廳的壁爐燃燒著劈啪作響的松木,清晨的鐘聲剛落,法官那冷峻的聲音穿透了厚重的晨霧:「昨夜平安夜,現在進入警長競選階段。」
這是標準九人局(三隻狼人、三位神職、三個平民)中最驚心動魄的局面——經典殘局之六:「雙預言家生推對決」。在屠邊規則的高壓下,手握 1.5 票生殺大權的警徽成了兩大陣營必爭的咽喉要道。全場貫徹嚴格的「絕發規則」,每個人只能在自己的輪次發言,無人得以插話。此時,長桌對面的 2 號玩家猛然舉起手牌起跳:「我是預言家,昨晚驗了 4 號是金水(好人),警徽流先 7 後 9!」
還沒等大家消化這個資訊,坐在我身旁的 5 號玩家冷笑了一聲,毫不猶豫地拍下桌子:「真預言家在此!昨晚查驗 2 號是個大鐵狼!他是在悍跳搶警徽,企圖給隊友發金水做身份!」
雙預言家對跳!
如果是在前幾世,身為 AI 魁儡的我肯定又會手足無措地向模型求救,生成幾段聽起來慷慨激昂、實則毫無邏輯支撐的辯詞。然而,在經歷了無數次被女巫無聲毒殺與白天公投放逐的死循環後,我們 2N1P 團隊清醒地意識到:悍跳狼之所以能用 AI 幻覺在發言中混淆視聽,根源在於系統缺乏不可妥協的「介面契約」與「狀態封裝」。 當任何人都能在發言時隨心所欲地竄改查驗歷史、捏造不存在的狀態遷移時,謊言便能在沒有編譯期防護的荒野中肆意生長。
那天散場後,我在廊下想了很久。
女巫那個永遠得不到答案的問題——「我到底毒對了沒有」——之所以無解,是因為那一夜沒有人翻牌、沒有人把結果寫下來。凡是只活在記憶和口頭裡的東西,隔一個晚上就可能對不上,甚至會被人悄悄改掉一筆而你渾然不覺;唯有被鄭重寫進一份誰都動不了的紀錄,那句話才追究得到。
七號那卷從不離身的羊皮紙,好像就是這樣一份東西——她記下的字,我從沒見過哪一輪被改動。而今天這場雙預言家對跳賭的也是同一件事:誰的查驗紀錄,是寫進了不可竄改的契約裡的?
在軟體工程領域,特別是面對大型開源專案與複雜的分散式架構時,物件導向程式設計(Object-Oriented Programming, OOP)的四大支柱不是教科書上的死背名詞,而是保護系統不被惡意操作或非預期狀態摧毀的護城河:
在「源來適你」(OpenSource4You)社群中,「開源大佬」chia7712 經常強調一個關鍵思維:優秀的代碼不只在理想路徑(Happy Path)下能夠運行,更重要的是在面對惡意輸入、併發競爭或異常狀態時,能展現出極致的韌性與拒絕非法的勇氣。
這正是 Bertrand Meyer 所倡導的契約式設計(Design by Contract, DbC)與防禦性編程的核心靈魂:
許多「Vibe Coder」仰賴 AI 寫出來的代碼之所以在生產環境瞬間崩潰,就是因為缺乏防禦性檢驗。AI 傾向於假設所有傳入的參數都是乾淨合法的,當惡意資料或邊界空值(Null)傳入時,系統便發生狀態污染。真正的防禦性編程,秉持著 Fail Fast(快速失敗)的鐵則:在異常發生的第一時間拋出例外,將災難遏止於搖籃之中。
在現代 Java 25 體系中,我們善用 record 的不可變性(Immutability)與介面契約,設計一套能夠在執行期徹底檢驗冒牌者的防禦程式碼。以下代碼嚴格遵循 100% 英文命名與註解標準:
package com.castronegro.contract;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
// InspectionRecord guarantees immutable audit records across rounds.
public record InspectionRecord(int round, String targetPlayerId, boolean isWerewolf) {
public InspectionRecord {
Objects.requireNonNull(targetPlayerId, "targetPlayerId must not be null");
if (round < 1) {
throw new IllegalArgumentException("round must be positive: " + round);
}
}
}
// ContractViolationException represents an illegal operation breaching the protocol.
class ContractViolationException extends RuntimeException {
public ContractViolationException(String message) {
super(message);
}
}
// SeerInspectionContract defines invariants and boundaries required for any seer.
public interface SeerInspectionContract {
InspectionRecord inspect(int currentRound, String targetPlayerId, boolean actualIdentity);
Map<String, InspectionRecord> getAuditLedger();
boolean hasInspected(String targetPlayerId);
}
// TrueSeer strictly respects immutability, state transitions, and round progression.
class TrueSeer implements SeerInspectionContract {
private final String seerId;
private int lastInspectedRound = 0;
private final Map<String, InspectionRecord> auditLedger = new HashMap<>();
public TrueSeer(String seerId) {
this.seerId = Objects.requireNonNull(seerId, "seerId must not be null");
}
@Override
public InspectionRecord inspect(int currentRound, String targetPlayerId, boolean actualIdentity) {
// Precondition 1: Monotonic round progression
if (currentRound <= this.lastInspectedRound) {
throw new ContractViolationException("Round regression detected. Last: " +
this.lastInspectedRound + ", Current: " + currentRound);
}
// Precondition 2: Self-inspection is forbidden
if (this.seerId.equals(targetPlayerId)) {
throw new ContractViolationException("Seer cannot inspect self: " + targetPlayerId);
}
// Precondition 3: Duplicate inspection verification
if (auditLedger.containsKey(targetPlayerId)) {
throw new ContractViolationException("Target already inspected: " + targetPlayerId);
}
InspectionRecord record = new InspectionRecord(currentRound, targetPlayerId, actualIdentity);
auditLedger.put(targetPlayerId, record);
this.lastInspectedRound = currentRound;
return record;
}
@Override
public Map<String, InspectionRecord> getAuditLedger() {
return Collections.unmodifiableMap(this.auditLedger);
}
@Override
public boolean hasInspected(String targetPlayerId) {
return auditLedger.containsKey(targetPlayerId);
}
}
// FakeWerewolfSeer attempts to fabricate conflicting records and hallucinates past rounds.
class FakeWerewolfSeer implements SeerInspectionContract {
private final Map<String, InspectionRecord> auditLedger = new HashMap<>();
@Override
public InspectionRecord inspect(int currentRound, String targetPlayerId, boolean actualIdentity) {
// Flawed logic: Accepts backward rounds and overwrites past claims arbitrarily
InspectionRecord fabricatedRecord = new InspectionRecord(currentRound, targetPlayerId, actualIdentity);
auditLedger.put(targetPlayerId, fabricatedRecord);
return fabricatedRecord;
}
@Override
public Map<String, InspectionRecord> getAuditLedger() {
return Collections.unmodifiableMap(this.auditLedger);
}
@Override
public boolean hasInspected(String targetPlayerId) {
return auditLedger.containsKey(targetPlayerId);
}
}
在這段程式碼中,TrueSeer 在內部封裝了完整的狀態檢查:
Collections.unmodifiableMap 返回不可變的稽核帳本,杜絕外部竄改。相對地,FakeWerewolfSeer 忽視了前置條件防禦,允許任意竄改歷史紀錄。當整合測試在 CI/CD 流水線執行防禦性驗證時,缺乏約束的實作便會在多執行緒與契約驗證器面前原形畢露。
換句話說,TrueSeer 的稽核帳本是一份 append-only、對外唯讀的紀錄:寫進去的每一筆都追究得到,連一次「重來」都抹不掉它。而 FakeWerewolfSeer 的 auditLedger 可以被任意 put 覆蓋,歷史隨時能被改寫,於是任何謊言都說得通。介面契約的前置條件,就是把「可竄改」與「不可竄改」這條界線,畫死在編譯期。
為了將契約約束視覺化,我們繪製出這張完整的 UML 類別關係圖。圖中清晰展示了抽象介面、不可變記錄物件以及具體實作在遇到非法狀態遷移時的防禦路徑:

在現代軟體工程中,Apache Kafka 的核心客戶端設計(例如 ProducerRecord 與 RecordMetadata)即是介面契約與防禦性編程的經典典範。當生產者發送訊息時,底層核心會在第一時間校驗 Topic 分區合法性、Key/Value 序列化邊界與壓縮演算法相容性;一旦前置條件不符,立刻丟出例外,絕不在內部吞噬錯誤或靠直覺猜測。更關鍵的是,Kafka 的分區日誌本身就是一份 append-only、只能追加不能修改的紀錄——一則訊息一旦寫入某個 offset,就永遠釘在那裡,任誰都改不動。這正是七號那卷羊皮紙在工程世界裡的對應物。
在由「AI 狼」與「真人工程師」交織的開源博弈中,維護者不可能依靠通靈去辨別 PR 提交者的動機。唯一的解法,就是用嚴格的型別介面、語義契約與單元測試斷言,將行為邊界死死釘在編譯期與測試網格中。 當冒牌者企圖用一段邏輯自相矛盾的程式碼呼嚨過去時,介面契約會像一面照妖鏡,第一時間讓他在 CI 階段爆紅出局。
讀完今天這篇文章,你應該要能夠:在面對任何業務邏輯或 AI 生成的代碼片段時,主動為其抽象出高階介面契約,明確定義前置條件、後置條件與狀態不變量,並以防禦性編程原則全面封裝關鍵狀態。
sealed 類別、record、instanceof 模式匹配——把「非法狀態不可表達」寫進語言層。record 解構模式、switch 模式匹配、循序集合(Sequenced Collections)、虛擬執行緒。super())、模組匯入宣告、精簡原始檔。"Design by Contract Bertrand Meyer invariants preconditions" "Java 21 record patterns sealed classes" "JDK 25 flexible constructor bodies validate before super" "defensive programming fail fast immutable record"
¹ 註:本書名為情境設定之虛構文獻,非真實歷史或開源紀錄。