健壮性不等于堆砌异常处理,应按异常性质选择受检或运行时异常:对可预见可恢复场景(如文件路径错误)用受检异常,对逻辑缺陷(如空指针)用运行时异常;封装底层异常为语义清晰的自定义异常;善用optional和空安全设计避免异常驱动流程。

健壮性不等于堆砌异常处理,受检异常的“冗长”感,往往来自用错了地方,而不是它本身有问题。
明确异常性质:该不该强制调用者处理?
受检异常(Checked Exception)的设计本意是提醒调用方“这个问题你大概率得管”。关键在判断:这个异常是否属于调用方可预见、可恢复的场景?
- 适合用受检异常:文件路径错误、网络超时、数据库连接失败——调用方可以重试、换路径、提示用户或降级返回缓存数据。
- 不适合用受检异常:空指针、数组越界、类型转换失败——这些是程序逻辑缺陷,应通过校验和测试提前暴露,而非靠异常机制兜底。
封装底层细节,暴露语义清晰的异常
直接把 IOException、SQLException 暴露给上层业务,会迫使每一层都写一堆 catch 和 throws,还容易导致异常信息丢失。更合理的方式是:
- 在数据访问层捕获原始受检异常;
- 根据业务上下文,转换为自定义的、带业务含义的受检异常(如 UserLoadFailureException);
- 新异常只声明真正需要调用方决策的场景,比如“用户不存在”是否要创建默认用户,“加载失败”是否允许展示空状态。
用运行时异常替代部分受检异常场景
并非所有“可能失败”的操作都需要受检异常。当失败原因本质是配置错误、环境缺失或上游服务不可用时,用 RuntimeException 更自然:
- 避免在每个 service 方法签名里加 throws ConfigNotFoundException;
- 改用 @PostConstruct 校验必要配置,启动时报错退出,比运行时层层抛异常更早暴露问题;
- 对第三方 API 调用,若其失败属于常态(如限流、临时抖动),用运行时异常配合重试机制(如 @Retryable),比强制上层 try-catch 更轻量。
善用 Optional 和空安全设计,减少异常驱动流程
很多本该用判断处理的场景,被写成“先查再 catch NoSuchElementException”,既低效又模糊意图。例如:
- 查询用户 → 返回 Optional
,调用方用 isPresent() 或 orElseThrow() 显式选择行为; - 解析 JSON → 使用 Jackson 的 @JsonInclude(NON_NULL) + 不抛异常的 readValue,配合前置 schema 校验;
- 避免为“找不到就抛异常”而设计接口,优先让方法语义自解释:“find” 可返回 null/Optional,“require” 才该抛异常。











