关键不是少写catch,而是每次捕获都需明确意图、具体异常类型和可执行恢复动作;应只捕获能处理的特定异常(如timeoutexception、filenotfoundexception),兜底逻辑统一收口至web层或网关,中间层泛捕exception即为缺陷。

避免过度捕获异常,关键不是少写 catch,而是让每次捕获都有明确意图、具体类型和可执行的恢复动作。泛捕 Exception 或 Throwable 会让空指针、内存溢出、线程中断等本该暴露的问题被静默吞掉,后续逻辑照常执行,状态错乱,故障极难定位。
只捕获你能真正处理的具体异常
每种异常代表一种可识别、可响应的失败语义。捕获前先问:这个异常发生时,我是否知道下一步该做什么?
- 调用 HTTP 接口超时 → 单独
catch (TimeoutException | IOException e),触发重试或返回缓存数据 - 读取配置文件失败 →
catch (FileNotFoundException e),加载默认配置并记录warn日志 - 数据库唯一键冲突 →
catch (SQLException e),检查e.getSQLState(),若为23505(PostgreSQL)或23000(MySQL),转为业务异常DuplicateOrderException - JSON 解析失败 → 不在业务层
catch,而是在工具方法safeParseJson()内部处理JsonProcessingException,对外统一抛出语义清晰的InvalidConfigException
兜底逻辑必须收口到统一入口
Web 层或网关是唯一允许做有限兜底的地方;Service、DAO、Util 等中间层出现 catch (Exception e) 就是代码缺陷。
- Spring 项目用
@ControllerAdvice+ 多个@ExceptionHandler分别处理ValidationException、HttpClientErrorException、BusinessException - RPC 客户端可在最外层
catch (Throwable t),但必须完整记录堆栈、快速失败(如返回预设兜底值),且禁止继续执行后续业务逻辑 - CI 流水线应配置静态检查规则(如 SonarQube 或自定义 Checkstyle),禁止
catch (Exception|Throwable)出现在非 Web 入口类中
用防御式编程减少“不得不 catch”的场景
很多泛捕源于前期校验不足或资源管理失控,结果只能靠 catch 来补救。
- 参数非法提前拦截:
Objects.requireNonNull(id, "id must not be null")、Preconditions.checkArgument(count > 0, "count must be positive") - 所有
AutoCloseable资源(文件流、HTTP client、数据库连接)一律用try-with-resources,避免手动close()引发二次异常覆盖主异常 - 调用第三方 SDK 前查文档,优先选用声明了细粒度异常的方法(如
executeWithTimeout()抛ApiTimeoutException,而非泛抛Exception)
别在 finally 中掩盖原始异常
如果 try 块已抛异常,而 finally 又抛新异常,原始异常会被彻底丢弃,调试时看不到真正出问题的位置。
- 首选
try-with-resources,它会自动将close()异常作为 suppressed exception 附加到主异常上 - 若必须手动关闭,应在
finally中判空并捕获关闭异常,不向上抛:if (resource != null) try { resource.close(); } catch (IOException ignored) {} - JDK7+ 可用
originalException.addSuppressed(closeException)显式保留,但日常开发中推荐前两种更稳妥的方式
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











