想象我们正在设计一个银行转账系统。转账失败确实是一个非常严重的问题,需要确保每个程序员都正确处理。让我们看看两种可能的设计方案:
方案1:使用受检异常
public class BankService {
// 使用受检异常强制处理转账失败的情况
public void transfer(Account from, Account to, BigDecimal amount)
throws InsufficientFundsException, AccountLockedException {
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException("余额不足");
}
if (from.isLocked() || to.isLocked()) {
throw new AccountLockedException("账户被锁定");
}
// 执行转账...
}
}
// 调用代码
public void processTransfer() {
try {
bankService.transfer(accountA, accountB, new BigDecimal("1000"));
} catch (InsufficientFundsException e) {
// 必须处理余额不足
} catch (AccountLockedException e) {
// 必须处理账户锁定
}
}
方案2:使用运行时异常(更常见的做法)
public class BankService {
// 使用运行时异常处理业务异常
public void transfer(Account from, Account to, BigDecimal amount) {
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException("余额不足");
}
if (from.isLocked() || to.isLocked()) {
throw new AccountLockedException("账户被锁定");
}
// 执行转账...
}
}
// 在合适的层级统一处理异常
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(InsufficientFundsException.class)
public ResponseEntity<ApiResponse> handleInsufficientFunds(InsufficientFundsException e) {
// 统一处理余额不足异常
return ResponseEntity.badRequest().body(
new ApiResponse("INSUFFICIENT_FUNDS", "余额不足,请检查账户余额")
);
}
}
为什么第二种方案更好?这里有几个重要的原因:
- 异常传播的灵活性
- 运行时异常可以自由传播到最适合处理它的层级
- 受检异常会强制每个中间层都声明throws或try-catch,导致代码污染
- 关注点分离
- 业务逻辑应该关注正常的业务流程
- 异常处理最好在统一的地方处理,而不是散布在各处
- 代码可维护性
// 使用受检异常时,添加新的异常类型需要修改所有调用链上的方法签名
public void transfer() throws InsufficientFundsException, AccountLockedException,
NewException1, NewException2 { // 需要不断添加新异常
// 转账逻辑
}
// 使用运行时异常时,只需在异常处理器中添加新的处理方法
@ExceptionHandler(NewBusinessException.class)
public ResponseEntity<?> handleNewException(NewBusinessException e) {
// 处理新的异常类型
}