Java 异常包装:不好的做法?

Java exceptions wrapping: bad practice?

来自 PHP 只有一种方法来编写异常处理的世界。我发现 Java 中的异常包装有点 "ugly":

public void exampleOneException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("Error...", e);
    }
}

我更喜欢使用这种风格:

public void exampleTwoException() {
    try {
        // do something
    } catch (MyBusinessException e) {
        log.error("Error...: " + e);
    } catch (NumberFormatException e) {
        log.error("Error...: " + e);
    }
}

这些处理异常的方法有什么区别或最佳实践吗?

这些都是针对两种不同情况的有效方法。

在第一种情况下,该方法无法对异常做任何智能处理,但它必须 "report" 向上处理。这样调用者可以捕获异常并决定如何处理它(例如取消流程,向用户弹出消息,记录它等)

在第二种情况下,您捕获异常并将其记录下来,从而有效地向调用者隐藏失败。如果调用者并不真正关心操作是否成功,这种类型的处理可能很有用。

我会说 NumberFormatExceptionMyBusinessException 都有用,但在不同的情况下。

它们通常出现在 class 层次结构的不同级别:例如 NumberFormatException 是较低级别的异常,您可能不想在较高级别(例如用户界面)公开它,如果它的用户无权从中恢复。在这种情况下,更优雅的做法是抛出 MyBusinessException 并显示一条信息性消息,例如解释上一步中的某些内容供应不当或发生某些内部处理错误并且 he/she 需要重新启动该过程.

另一方面,如果您的函数用于中间级别(例如 API)并且开发人员有办法从异常行为中恢复,NumberFormatException 更有用,因为它可以通过编程方式处理,并且应用程序的流程可能会以最小的中断继续(例如,提供默认的有效数字)。或者,这可以指示代码中的 flaw/bug 应该被修复。

有关如何遵循使用异常的最佳实践的详细信息,请阅读 Effective Java by Joshua Bloch 中的 Item 61 - Throw exceptions appropriate to the abstraction

第一个示例通常被视为更好的方法。

您也不应将 MyBusinessException 视为 包装 NumberFormatException,而 NumberFormatException MyBusinessException.

的原因

异常应该适用于公开的接口。接口的调用者不需要知道或处理实现细节。除非 NumberFormatException 在调用 exampleOneException 时真正作为一种错误类型有意义,否则应该将其转换为更合适的异常。

一个更具体的例子通常包括不同的实现,其中接口的用户不需要处理实现细节(在编译时甚至可能不知道)。

interface MyRepository {
    Object read(int id) throws ObjectNotFoundException;
}

// a sql backed repository
class JdbcRepository implements MyRepository {
    public Object read(int id) throws ObjectNotFoundException {
        try { ... }
        catch (SQLException ex) {
            throw new ObjectNotFoundException(ex);
        }
    }
 }

// a file backed repository
class FileRepository implements MyRepository {
    public Object read(int id) throws ObjectNotFoundException {
        try { ... }
        catch (FileNotFoundException ex) {
            throw new ObjectNotFoundException(ex)
        }
    }
}

因为接口声明了它可以return的错误类型,该接口的客户可以是一致的和理性的。添加代码来处理 FileNotFoundExceptionSQLException 那么实际的实现可能是其中之一,也可能两者都不是,这并不好。

考虑在 FileRepository 的实现中是否有多个地方可能会抛出 FileNotFoundException。这是否意味着它们中的每一个都意味着 找不到对象

在考虑选项二 exampleTwoException 时,重要的是要意识到您的 catch 块有效地说明发生的任何错误的影响都已减轻。有 种情况下检查的异常被合理地忽略了,但更有可能的是一个简单的

try { ... }
catch (SomeException ex) {
    log.error("caught some exception", ex);
}

其实是开发者没有考虑到异常的后果,或者代码应该包含一个FIXME.

当你看到 catch (Exception ex) 或不合情理的 catch (Throwable ex) 时更是如此。

最后,您是否愿意成为深入挖掘应用程序以找到您需要添加(还)另一个 catch 块以处理新实现的所有位置的人?也许这是 catch (Exception ex)...

的原因

总是 抛出适合抽象的异常

从清晰可靠的代码的角度来看,您的方向不对。
首先,对于已经由方法的 header 抛出的 Exception,您不需要使用 try/catch。所以在你的第一种情况下,代码应该如下所示:

 public void exampleOneException(String input) throws MyBusinessException {
    // do something
 }

这个更漂亮不是吗? 在上面的例子中,如果 MyBusinessException 发生,那么异常将被吞噬,所以用户永远不会看到发生了什么。如果您希望发生这种情况,那么这是您的最佳做法。

第二种情况就清楚了。您捕获两个不同的异常并处理它们 (通过日志记录)一般来说这是一种更好的做法。

因此,两种不同情况的最佳做法取决于您想要实现的目标。关于美,这完全是主观的。