Skip to content

异常处理最佳实践

异常处理看起来简单,但里面全是坑。这篇文章帮你把这些坑一个个踩平。

三种异常类型

Throwable
├── Error (系统错误,程序无法处理)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── VirtualMachineError
└── Exception (可处理的异常)
    ├── RuntimeException (运行时异常)
    │   ├── NullPointerException
    │   ├── IndexOutOfBoundsException
    │   ├── IllegalArgumentException
    │   └── ClassCastException
    └── 受检异常 (Checked Exception)
        ├── IOException
        ├── SQLException
        └── FileNotFoundException

try-catch-finally 执行顺序

java
try {
    System.out.println("try 块");
} catch (Exception e) {
    System.out.println("catch 块");
} finally {
    System.out.println("finally 块");
}

// 无异常:try → finally
// 有异常:try → catch → finally

finally 不执行只有一种情况:System.exit() 终止了 JVM。

避坑指南

不要捕获 Throwable

java
// ❌ 捕获所有异常——Error 不该被捕获
try {
    // ...
} catch (Throwable t) {
    // Error 不应该捕获
}

// ✅ 只捕获预期的异常
try {
    // ...
} catch (IOException e) {
    // 处理 IOException
}

先捕获子类后捕获父类

java
try {
    // ...
} catch (FileNotFoundException e) {
    // 先捕获具体的异常
} catch (IOException e) {
    // 后捕获父类异常
}

不要吞掉异常

java
// ❌ 吞掉异常——出了 bug 都不知道在哪
try {
    // ...
} catch (Exception e) {
    // 什么都不做
}

// ✅ 至少记录日志
try {
    // ...
} catch (Exception e) {
    log.error("操作失败", e);
    throw e;
}

不要用异常控制流程

java
// ❌ 用异常做流程控制——慢、丑、容易出错
for (int i = 0; i < 1000; i++) {
    try {
        Integer.parseInt(someString);
    } catch (NumberFormatException e) {
        // 处理
    }
}

// ✅ 先验证再执行
if (isValidNumber(someString)) {
    Integer.parseInt(someString);
}

最佳实践

使用 try-with-resources

java
// ❌ 传统方式容易漏 close
BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("file.txt"));
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    log.error("读取文件失败", e);
} finally {
    if (br != null) {
        try {
            br.close();
        } catch (IOException e) {
            log.error("关闭流失败", e);
        }
    }
}

// ✅ try-with-resources 自动关闭
try (BufferedReader br = new BufferedReader(
        new FileReader("file.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    log.error("读取文件失败", e);
}

包装异常

java
// ❌ 直接抛出原始异常暴露实现细节
try {
    dao.save(user);
} catch (SQLException e) {
    throw e;
}

// ✅ 包装为业务异常
try {
    dao.save(user);
} catch (SQLException e) {
    throw new ServiceException("保存用户失败", e);
}

提供有意义的异常信息

java
// ❌ 空异常——让人摸不着头脑
throw new IllegalArgumentException();

// ✅ 附带上下文
throw new IllegalArgumentException(
    "Invalid user id: " + userId + ". User id must be positive.");

合理使用断言

java
// ❌ 运行时检查可能忘记调用
if (object == null) {
    throw new IllegalArgumentException();
}

// ✅ 断言用于开发测试阶段检查
assert object != null : "object must not be null";

异常处理策略

策略说明使用场景
记录并重新抛出记录日志后重新抛出异常需要上层处理
包装异常包装为业务异常隐藏实现细节
恢复尝试恢复执行可恢复的错误
降级提供默认值非关键功能

自定义业务异常

java
public class BusinessException extends RuntimeException {
    private final String code;

    public BusinessException(String message) {
        super(message);
        this.code = "BUSINESS_ERROR";
    }

    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
    }

    public BusinessException(String code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}
java
public void withdraw(Account account, double amount) {
    if (amount <= 0) {
        throw new BusinessException("INVALID_AMOUNT", "金额必须大于0");
    }

    if (account.getBalance() < amount) {
        throw new BusinessException("INSUFFICIENT_BALANCE", "余额不足");
    }

    // ...
}

基于 VitePress 构建