首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 自定義異常最佳實踐:設計、拋捕、異常鏈與常見反模式

     編輯:Java 相關知識

自定義異常是領域模型表達業務錯誤語義的關鍵手段。本文涵蓋設計原則、四步實現、異常鏈保留、序列化考量、常見反模式與單元測試策略。

一、設計原則

  • 語義清晰:異常名即業務錯誤碼(如 InsufficientBalanceException 優於 BusinessException)。
  • 繼承選擇:業務異常繼承 RuntimeException(非受檢,框架統一攔截);可恢復的系統異常繼承 Exception(受檢,強制處理)。
  • 豐富上下文:攜帶錯誤碼、參數快照、鏈路追蹤 ID,便於診斷。
  • 不可變:字段 final,構造器注入,無 setter。

二、標準實現模板

public class InsufficientBalanceException extends RuntimeException {
    private final String accountId;
    private final BigDecimal required;
    private final BigDecimal available;
    private final String errorCode = "ACCT_001";

    public InsufficientBalanceException(String accountId, BigDecimal required, BigDecimal available) {
        super(String.format("賬戶 %s 餘額不足:需 %.2f,可用 %.2f", accountId, required, available));
        this.accountId = accountId;
        this.required = required;
        this.available = available;
    }

    // 異常鏈構造器(保留根因)
    public InsufficientBalanceException(String accountId, BigDecimal required, BigDecimal available, Throwable cause) {
        this(accountId, required, available);
        initCause(cause);
    }

    // Getters 供上層處理器提取結構化信息
    public String getAccountId() { return accountId; }
    public BigDecimal getRequired() { return required; }
    public BigDecimal getAvailable() { return available; }
    public String getErrorCode() { return errorCode; }
}

三、拋捕最佳實踐

// Service 層拋出
public void transfer(String from, String to, BigDecimal amount) {
    Account fromAcc = accountRepo.find(from);
    if (fromAcc.getBalance().compareTo(amount) < 0) {
        throw new InsufficientBalanceException(from, amount, fromAcc.getBalance());
    }
    // ...
}

// Controller/全局異常處理器捕獲
@ExceptionHandler(InsufficientBalanceException.class)
public Result handle(InsufficientBalanceException e) {
    log.warn("轉賬失敗: {}", e.getMessage(), e); // 保留堆棧
    return Result.fail(e.getErrorCode(), e.getMessage());
}

四、常見反模式 ❌

反模式危害正確做法
捕獲後 throw new RuntimeException(e.getMessage())丟失堆棧、異常類型、原因鏈throw new BizException("msg", e) 保留 cause
用異常控制業務流程(如「查無數據」拋異常)性能差、可讀性低返回 Optional/Result 或空集合
定義過於通用的 BaseException 並捕獲它掩蓋具體錯誤,難以差異化處理針對性捕獲具體異常類
異常類無 serialVersionUID 或非 final 字段分布式序列化失效/狀態不一致實現 Serializable,字段 final
靜態常量異常實例(如 static final MyException EX = new MyException())堆棧固化在類加載時,誤導排查每次 new 獲取當前堆棧

五、單元測試驗證

@Test
void transfer_insufficientBalance_throwsException() {
    when(repo.find("A")).thenReturn(new Account("A", BigDecimal.TEN));
    assertThatThrownBy(() -> service.transfer("A", "B", BigDecimal.valueOf(20)))
        .isInstanceOf(InsufficientBalanceException.class)
        .extracting("accountId", "required", "available")
        .containsExactly("A", BigDecimal.valueOf(20), BigDecimal.TEN);
}

六、序列化與分布式傳遞

  • 實現 Serializable,聲明 private static final long serialVersionUID = 1L;。
  • 避免非 transient 引用不可序列化對象(如 Connection、ThreadLocal)。
  • 跨服務傳遞時,建議在網關層轉為統一錯誤碼 + 消息,避免客戶端依賴服務端異常類。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved