异常不该用来控制程序流程。它会导致变量状态不可预测、生命周期失控、多线程下数据混乱、副作用无法回滚、异步中结果不可控、继承中初始化契约被破坏,掩盖本该早期暴露的问题。

异常不该用来控制程序流程。把它当 if-else 用,表面能跑通,实际会让业务逻辑变量状态不可预测、难以追踪,尤其在多线程或复杂调用链中,极易引发数据混乱。
用异常代替条件判断,直接破坏变量生命周期
常见写法是:靠捕获 NullPointerException 或 KeyError 来判断某个对象是否存在,而不是提前检查 null 或 in 操作。这种写法让变量的初始化、赋值、使用时机完全脱离代码主干,变成“被动触发”。一旦异常路径未覆盖清理逻辑,变量就可能残留旧值、空引用或中间态。
- 比如用户信息查询方法,不先判空,而是等访问 user.getName() 抛 NPE 后再兜底设默认名——此时 user 对象本身可能已部分构造,后续其他字段被误读
- 又如配置加载,用 try-catch FileNotFoundError 来决定是否启用默认配置,但文件读取失败时,缓存变量仍保留上一次成功加载的旧配置引用,导致新请求拿到过期数据
循环中抛异常中断流程,导致集合/计数器状态错位
典型反例是在 for 循环里对每个元素做处理,一旦某次处理失败(如金额校验不通过)就 throw IllegalArgumentException,并期望外层统一回滚。问题在于:异常发生前,已有部分元素被修改、累加或写入临时变量,而这些副作用不会自动撤销。
- 订单批量审核场景:遍历 100 笔订单,第 42 笔因余额不足抛异常。此时前 41 笔的状态已更新为“审核中”,但事务未提交;异常后若无显式回滚,这些中间状态会滞留数据库,造成数据不一致
- 计数类逻辑更隐蔽:for (int i = 0; i
异步+异常流程混用,变量作用域彻底失控
在 Promise、CompletableFuture 或协程中,用异常跳转替代状态机设计,会使变量绑定关系断裂。JavaScript 和 Java 中都出现过这类问题:同一个局部变量,在不同 then/catch 分支中被多次赋值,但各分支执行时机不确定,最终值取决于调度顺序而非业务逻辑。
- Node.js 示例:let result; apiCall().then(data => { result = parse(data); }).catch(err => { result = getDefault(); }); setTimeout(() => console.log(result), 100); ——result 可能是 undefined、解析结果或默认值,完全不可控
- Java CompletableFuture 链中,若在 handle() 里 throw 异常来切换分支,原始 supplier 中声明的局部变量无法跨阶段延续,强行复用会导致空指针或脏数据
继承与重写场景下,异常掩盖变量初始化顺序
父类构造器中调用可被子类重写的方法,而该方法内部又依赖尚未初始化的子类字段——此时若用异常兜底(如捕获 NullPointerException 并返回空),等于主动绕过 Java 初始化契约,使 this 引用处于半成品状态。
- 在线人员列表反例中,父类 BaseUserManager 在 init() 中调用 getUserList(),子类重写了它并访问了未初始化的 @Autowired RedisTemplate 字段;捕获 NPE 后返回空列表,表面不报错,实则后续所有缓存操作都基于一个未注入的 null 实例
- 变量混乱本质不是值错了,而是“该有的没初始化,不该有的被误用”,异常掩盖了本该在编译期或启动期暴露的问题

















