【问题标题】:"switch" equivalent for exception handling异常处理的“switch”等价物
【发布时间】:2017-09-18 13:43:13
【问题描述】:

这不是关于一般异常处理的问题,但它专门适用于某些框架的使用。几个典型起点的例子:

  • GWT:public void onFailure(Throwable caught) 实现 AsyncCallback 接口。
  • JAX-RS:public Response toResponse(E throwable) 实现 ExceptionMapper<E extends Throwable> 接口。

上述两种方法都接收Throwable 的实例。通常,我看到开发人员使用简单的“if/else if”块来区分处理逻辑:

// As specified by the AsyncCallback class of the GWT framework
public void onFailure(Throwable caught) {
    if (caught instanceof AnException) {
        // handle AnException
    } else if (caught instanceof AnotherException) {
        // handle AnotherException
    } else if (caught instanceof YetAnotherException) {
        // handle YetAnotherException
    } else if (caught instanceof ...) {
        // and so on...
    }
}

由于许多原因我不喜欢“if/else if”块,我想出了以下“模式”,它将“if/else if”块转换为“try/catch”块,表现就好像它是一个“开关”块:

public void onFailure(Throwable caught) {
    try {
        throw caught;
    } catch(AnException e1) {
        // handle AnException
    } catch(AnotherException e2) {
        // handle AnotherException
    } catch(YetAnotherException e3) {
        // handle YetAnotherException
    } catch(...) {
        // and so on...
    }
}

我的问题是:在性能、最佳实践、代码可读性、一般安全性或其他任何我没有考虑或注意到的方面 - 使用这种方法是否有任何缺点?

【问题讨论】:

  • 另一种方法是将不同的逻辑放入异常类型本身(如果它们是您自己的),这样您就可以有一个子句。事实上,如果大多数异常是您自己的已检查异常,我会建议您这样做。很多 if/else/switch 行为最好是多态
  • @Novaterata 是的,这种情况是使用 JAX-RS 框架处理的,您可以在其中创建特定的 ExceptionMapper<E extends Throwable> 实现来处理您自己的检查异常。但有时框架会强制您只处理 Throwable(例如 GWT)。
  • 我觉得它看起来不错。一个小缺点是如果您忘记了“包罗万象”子句并将错误从错误处理代码“发送回”框架。有朝一日可能会导致艰难的调试会话。
  • @jannis 目的不是流量控制,而是异常处理。如果我不使用提到的框架,我什至不会费心发布这样的问题。框架要求我按照自己的规则工作,所以我需要适应。我编辑了我的问题,添加了粗体字以指出我在寻找什么。

标签: java exception-handling


【解决方案1】:

正常情况下使用异常来指导程序流是一种代码味道,但这并不是你真正要做的。我认为您可以摆脱这种情况有几个原因:

  1. 我们已经出于各种原因捕获并重新抛出异常(例如,“捕获、采取某些行动、传播”)。这在意图上有点不同,但在成本方面并不差。

  2. 您已经承担了引发此异常至少一次的成本。您可能已经承担了其原因被抛出、捕获、包裹或重新抛出的成本。填充堆栈跟踪的成本已经支付。再次抛出已填充的异常不会增加复杂度。

  3. 您没有使用异常来引导正常代码路径的流程。您正在对错误做出反应,因此您已经在 exceptional 路径上,并且您应该很少(如果有的话)在这里结束。如果这种模式效率低下,除非您遇到 很多 异常,否则这将无关紧要,在这种情况下,您会遇到更大的问题。花时间优化你期望走的路,而不是你不想走的路。

  4. 从美学上讲,很少有东西能让我的皮肤像if/else if 块的长链一样爬行,尤其是当条件只是类型检查时。在我看来,您提出的建议更具可读性。有多个有序的catch 子句很常见,因此结构大部分都很熟悉。 try { throw e; } 序言可能是非正统的,但很容易推理。

在传播Throwable 时要小心。一些错误,例如VirtualMachineError 层次结构,表明某些事情已经严重错误,应该允许他们继续前进。其他的,比如InterruptedException,传达一些关于originating线程状态的信息,它们不应该在不同的线程上盲目传播。有些,比如ThreadDeath,涵盖了这两个类别。

【讨论】:

  • '使用异常进行流控制'是一个重言式。它们流控制的。这个原则最初适用于在方法中使用异常作为一种goto。它确实适用于这种情况,但不适用于大多数引用它的情况(例如EOFException)。
  • @EJP 是的,异常总是流控制。但它们适用于异常流控制,而不是正常流控制。在非异常情况下使用它们直接程序流是一种代码味道,但这不是 OP 正在做的事情。
【解决方案2】:

只有在抛出大量错误时,性能才重要。它不会影响成功案例中的性能。与处理它们所花费的时间相比,存在如此多的错误将是一个更大的问题。

如果你调用一个本地方法,并且它抛出了一个异常,那么使用 catch 块来处理它就可以了。这是做同样的事情,但使用远程方法。这不是正常的控制流,因为在到达方法之前已经从 RPC 调用中抛出了异常,因此可以使用通常的异常处理构造。

编译器可以进行一些检查,例如检查是否首先列出了最具体的异常,但是使用一般的Throwable 类型会失去一些类型安全性。由于框架,这是不可避免的。

这里的任何一个例子都可以,因为它没有太大的区别。

【讨论】:

    【解决方案3】:

    您展示的两个代码块实际上表面上非常相似:乍一看,它们都是相同的“形状”。

    值得注意的是,if/else 链实际上比 try/catch 版本的代码行更少,更易于理解。

    我不认为 try/catch 版本本身是错误,但是当像这样并排比较时,我看不出它有任何理由更好 em> 要么。

    在所有其他条件相同的情况下,无争议代码总是比有争议代码更好:您永远不希望您的代码阅读者分心什么您的代码是按照您选择的方式执行的。

    【讨论】:

      猜你喜欢
      • 2017-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-22
      • 1970-01-01
      • 2010-10-10
      • 1970-01-01
      • 2015-06-24
      相关资源
      最近更新 更多