关于从方法返回Exception的技术规避原因咨询
先看你给出的示例代码:
Exception f() { try { // Do something. return null; } catch(Exception e) { return e; } }
虽然你知道这是不良实践,但下面这些技术原因是你必须避免这种写法的核心理由:
违背异常机制的设计语义
编程语言引入异常,就是用来区分「正常业务返回」和「意外错误场景」的。返回值的职责是传递预期内的计算结果,而异常是用来标记预期外的故障。把Exception当作返回值,直接混淆了这两个完全不同的语义范畴,等于把语言内置的错误处理模型给绕开了,完全违背了设计初衷。调用方极易遗漏错误处理
如果方法返回Exception,编译器不会强制调用方去处理这个“错误”——调用方可能随手就把返回值丢在一边,根本不知道这里发生了异常。这种情况下,错误会被悄悄隐藏,直到后续流程出现更严重的问题才暴露,排查起来要耗费大量时间。而抛出异常的话,编译器会强制要求调用方要么捕获处理,要么声明抛出,从机制上避免了错误被忽略。不必要的性能开销
创建Exception对象的成本远高于普通对象:Exception在实例化时会自动捕获当前线程的栈跟踪信息,这个过程需要遍历调用栈、收集大量上下文数据。如果把Exception当作返回值频繁创建和传递,会带来额外的性能损耗,尤其是在高频调用的方法中,这种开销会被明显放大。无法利用异常的分层处理能力
多数语言的异常都有继承体系(比如Java里的CheckedException、RuntimeException及其子类),抛出异常时,上层调用方可以针对不同类型的异常做精细化处理。但如果返回Exception对象,调用方只能拿到一个通用的实例,要判断具体错误类型必须额外做instanceof判断,代码会变得臃肿且容易出错,完全浪费了语言内置的异常分层机制。调试定位难度飙升
当异常被当作返回值传递时,它的栈跟踪是在创建时生成的,但错误被发现可能已经经过了好几层调用。如果是抛出异常,栈跟踪会完整展示错误发生的整个调用链路;而返回的Exception栈跟踪只记录了它被捕获的位置,后续的调用链路信息完全丢失,很难快速定位错误的根源。
内容的提问来源于stack exchange,提问作者Eleno

