在企業級軟體開發中,Java 程式碼經常需要與外部元件互動,無論是舊有系統、第三方函式庫或是外部服務。使用不可控的元件會引入非預期結果的風險。IBM IT 專家 Filippo Diotalevi 將展示面向切面程式設計(AOP)如何協助開發人員在保持程式碼整潔與彈性的同時,設計並定義元件之間的明確契約,從而降低這類風險。
DbC 是物件導向軟體設計的一項技術,旨在保障軟體品質、可靠度與可重用性。其核心在於:
這些相互責任稱為「契約」,透過「斷言」在程式執行的特定點驗證布林表達式必須為真。斷言失敗通常意味著軟體缺陷,必須回報給開發人員。DbC 在處理外部元件、需保證傳入/傳出資料正確性時特別有用。
Java 1.4 雖引入 assert 語句,但在日常編碼中直接在業務邏輯中加入前置/後置斷言,會造成「糾纏的程式碼」——混合業務邏輯與非功能性程式碼,導致模組化與可重用性嚴重受損,且無法在不修改應用程式碼的情況下調整斷言。
AOP 的目標是將橫切關注點(如前置條件、後置條件、不變量)分離到獨立模組,以彈性、宣告式方式套用。本文假設讀者具備 AspectJ 基礎知識。

此設計確保高度彈性:無需修改契約實作或應用程式碼即可套用/移除契約,且對應用程式完全透明。

契約是實作特定介面的 Java 類別,「橋樑」是 AspectJ 切面。切面指定套用契約的精確切點與所需邏輯。由於所有契約的邏輯圖相同,可開發共用的抽象切面。

AbstractContract 切面:定義控制邏輯(around() advice),留下抽象切點與契約管理器供具體實作ConcreteContract 切面:繼承抽象切面,實作 targetPointcut() 指定檢查位置,實作 getContractManager() 回傳具體契約管理器public interface ContractManager {
public void checkPreConditions(Object thisObject, Object[] args) throws ContractBrokeException;
public void checkPostConditions(Object thisObject, Object returnValue, Object[] args) throws ContractBrokeException;
public void checkInvariants(Object thisObject) throws ContractBrokeException;
}
三個方法分別檢查前置條件、後置條件、不變量。thisObject 存取呼叫端物件,前置/後置條件可見參數 args,後置條件額外可見 returnValue。
public abstract aspect AbstractContract {
public abstract pointcut targetPointcut();
public abstract ContractManager getContractManager();
Object around(): targetPointcut() {
ContractManager cManager = getContractManager();
if (cManager != null) {
cManager.checkInvariants(thisJoinPoint.getTarget()); // 初始不變量檢查
cManager.checkPreConditions(thisJoinPoint.getTarget(), thisJoinPoint.getArgs()); // 前置條件
}
Object obj = proceed(); // 執行目標方法
if (cManager != null) {
cManager.checkPostConditions(thisJoinPoint.getTarget(), obj, thisJoinPoint.getArgs()); // 後置條件
cManager.checkInvariants(thisJoinPoint.getTarget()); // 結束不變量檢查
}
return obj;
}
}
注意:不變量檢查執行兩次(前/後),可驗證服務執行是否影響外部欄位。契約失敗拋出 ContractBrokeException 中止執行。
假設需呼叫外部 CRM 系統:Customer cus = companyCustomerSystem.getCustomer("Pluto");。開發具體切面 CcCompanySystem 繼承 AbstractContract:
public aspect CcCompanySystem extends AbstractContract {
public pointcut targetPointcut(): call(Customer CompanySystem.getCustomer(String));
public ContractManager getContractManager() {
return new CompanySystemContractManager();
}
}
實作 CompanySystemContractManager 進行具體檢查:
public class CompanySystemContractManager implements ContractManager {
public void checkPreConditions(Object thisObject, Object[] args) throws ContractBrokeException {
if (args[0] == null) throw new ContractBrokeException("前置條件錯誤: 參數不得為 null");
}
public void checkPostConditions(Object thisObject, Object value, Object[] args) throws ContractBrokeException {
if (value == null) throw new ContractBrokeException("後置條件錯誤: 回傳值不得為 null");
}
public void checkInvariants(Object thisObject) throws ContractBrokeException {
// 不變量檢查邏輯
}
}
編譯後,契約將自動套用至每次 getCustomer 呼叫。若 ContractManager 設計夠通用,僅需重新定義 targetPointcut 即可重用於其他契約檢查。
ContractManager 可多情境重用本文展示使用 AspectJ 與 AOP 在 Java 開發中實踐 DbC 的可行路徑。該方案以極簡且高度模組化的設計,實現契約與業務邏輯分離編寫、宣告式套用,提供乾淨靈活的解決方案。