异常屏蔽是让错误静默失效而非不报错,典型表现为空catch、宽泛捕获exception、finally中抛新异常覆盖原始异常、异常包装丢失cause,必须记录完整堆栈、分层捕获、使用try-with-resources、保留cause并正确打日志。

异常屏蔽不是“没报错”,而是让错误悄悄失效——它不崩溃,却让问题彻底消失在日志里、监控中和排查路径上。最典型的场景,就是捕获受检异常(如 IOException、SQLException)后不做实质性处理。
空 catch 块:最危险的“静默删除”
写一个 catch (IOException e) { } 看似无害,实则切断了所有线索。异常对象被创建、抛出,又被立即丢弃,连堆栈都没留下。线上出问题时,你既看不到错误时间点,也找不到触发位置,更无法关联业务上下文。
- 绝对禁止只写花括号或仅调用
System.out.println("error") - 哪怕暂时没想好怎么处理,也必须至少记录完整堆栈:
logger.error("文件读取失败", e) - 若真要忽略某类已知异常(例如清理资源时的
InterruptedException),需显式恢复中断状态并加注释说明
宽泛捕获:用 Exception 吞掉本该暴露的问题
受检异常本意是提醒开发者“这事外部可能出错,你得应对”。但写成 catch (Exception e),就把 NullPointerException、IllegalArgumentException 这类明显属于代码缺陷的运行时异常也一并罩住,掩盖了本应在测试阶段就发现的逻辑漏洞。
- 优先按具体类型分层捕获:先
FileNotFoundException,再SecurityException,最后才是更宽泛的IOException - 不要在业务方法里写兜底
catch (Exception e);真需要统一处理,应放在框架顶层(如@ControllerAdvice) - 捕获后别只打
e.getMessage()——它可能为空,也不含堆栈;必须把异常对象作为日志方法的第二个参数传入
finally 中二次抛异常:覆盖原始错误
try 块已因数据库连接超时抛出 SQLException,结果 finally 里关流又触发 IOException,JVM 会直接丢弃前者,只抛后者。你看到的永远是“关流失败”,而真正的根因——连接超时——彻底丢失。
- finally 只做资源释放,不做可能抛异常的操作;若
close()可能失败,需单独 try-catch 并记录,但不向上抛 - 推荐使用
try-with-resources,自动管理资源且能正确合并多个异常(通过addSuppressed) - 手动关闭时,对每个资源单独处理,避免一个 close 失败影响后续释放
异常包装丢失 cause:断掉调用链
把原始异常简单包装成新异常却不传 cause,就像把事故报告撕掉第一页——日志里只剩“服务调用失败”,看不到底层是 SQL 语法错还是网络超时。
- 所有自定义异常包装必须使用带 cause 的构造函数:
throw new ServiceException("下单失败", e) - 检查所用框架是否保留 cause(如 Spring JDBC 的异常会自动包装并携带原异常)
- 日志中避免字符串拼接异常:
logger.error("失败:" + e)会丢失堆栈,必须用logger.error("失败", e)










