返回异常是反模式吗?

我有两个简单的方法:

public void proceedWhenError() {
   Throwable exception = serviceUp();

   if (exception == null) {
      // do stuff
   } else {
      logger.debug("Exception happened, but it's alright.", exception)
      // do stuff
   }
}

public void doNotProceedWhenError() {
   Throwable exception = serviceUp();

   if (exception == null) {
      // do stuff
   } else {
      // do stuff
      throw new IllegalStateException("Oh, we cannot proceed. The service is not up.", exception)
   }
}

第三种方法是私有帮助器方法:

private Throwable serviceUp() {
    try {
        service.connect();
        return null;
    catch(Exception e) {
       return e;
    }
}

我们和我的一位同事闲聊了聊这里使用的模式:

serviceUp() 方法返回异常(或可抛出)对象

第一种意见:

使用 Exceptions 来控制工作流是一种反模式,我们只应该从 Exception 对象本身返回布尔值,而不应该返回 Exception 对象本身。论点是,使用异常来控制工作流是一种反模式。serviceUp()

第二种意见:

没关系,因为我们需要在前两种方法中以不同的方式处理对象,并且返回异常对象还是布尔值根本不会改变工作流

你认为1)或2)是正确的,特别是,为什么?请注意,问题仅与方法及其返回类型 - vs 对象有关。serviceUp()booleanException

注意:我不是在质疑是使用可抛出对象还是异常对象。


答案 1

仅当异常在非异常情况下引发时才使用异常来定向流是一种反模式*。例如,在到达集合末尾时通过引发异常来结束循环是一种反模式。

另一方面,使用实际异常控制流是异常的良好应用。如果方法遇到无法处理的特殊情况,则应引发异常,从而将调用方中的流重定向到异常处理程序块。

从方法中返回“裸”对象,而不是扔掉它,肯定是违反直觉的。如果需要将操作的结果传达给调用方,更好的方法是使用包装所有相关信息的状态对象,包括异常:Exception

public class CallStatus {
    private final Exception serviceException;
    private final boolean isSuccess;
    public static final CallStatus SUCCESS = new CallStatus(null, true);
    private CallStatus(Exception e, boolean s) {
        serviceException = e;
        isSuccess = s;
    }
    public boolean isSuccess() { return isSuccess; }
    public Exception getServiceException() { return serviceException; }
    public static CallStatus error(Exception e) {
        return new CallStatus(e, false);
    }
}

现在,调用方将收到来自 :CallStatusserviceUp

CallStatus status = serviceUp();
if (status.isSuccess()) {
    ... // Do something
} else {
    ... // Do something else
    Exception ex = status.getException();
}

请注意,构造函数是私有的,因此返回或调用 。serviceUpCallStatus.SUCCESSCallStatus.error(myException)

*什么是例外的,什么是不例外的,很大程度上取决于环境。例如,非数值数据会导致 中的异常,因为它认为此类数据无效。但是,相同的确切数据不会导致方法异常,因为它是完全有效的。ScannernextInthasNextInt


答案 2

第二意见(“没关系”)不成立。代码不是正确的,因为返回异常而不是抛出它们并不是真正的惯用语。

我也不相信第一种意见(“使用异常来控制工作流程是反模式的”)。 正在引发异常,您必须对此异常做出反应 - 因此这实际上是流控制。返回一个或一些其他状态对象并处理它而不是处理异常 - 并且认为它不是基于异常的控制流是幼稚的。另一个缺点是,如果您决定重新抛出异常(包装或其他任何内容),您将不再拥有原始异常。当你试图分析实际发生的事情时,这是非常糟糕的。service.connect()booleanIllegalArgumentException

所以我会做经典的:

  • 在 中引发异常。serviceUp
  • 在调用的方法中:serviceUp
    • try-catch,记录调试并吞没异常(如果要继续执行异常)。
    • try-catch并重新抛出包裹在另一个异常中的异常,从而提供有关所发生情况的更多信息。或者,如果您无法添加任何实质性内容,则只需让原始异常传播。throws

最重要的是不要丢失原始异常。


推荐