重新抛出 Exception 的原因是否可以接受?

Is it acceptable to rethrow Exception's cause?

有时我想向用户抛出一个包含更多信息的异常,这样他们就可以很容易地看出方法失败的原因。

我的方法如下所示:

public myPublicMethod(...) throws Exception1, Exception2 {
  try {
    doSomething(); // this throws OtherException
  }
  catch (OtherException e) {
    Exception cause = e.getCause()
    if (cause instanceof Exception1) {
      throw (Exception1) cause;
    }
    if (cause instanceof Exception2) {
      throw (Exception2) cause;
    }
    throw new IllegalStateException("some other type of exception occurred", cause);
  }
}

这样的设计可以接受吗?

你的想法很好,但是依我拙见,你的实现方式有点不对劲,我会尽力解释的。

您一开始就声称要为用户提供有关异常的更多信息。你做了什么 - 从它的“父异常”中剥离原因(e.getCause() 然后抛出原因) - 实际上给用户 less 信息,因为 [=11] 中的信息=],您丢弃的可能包含有关导致程序崩溃的流程的有用详细信息。

  1. 捕获最具体的异常并将其抛给调用者是一种很好的做法,但请确保您没有遗漏细节。抛出cause而没有parent exception,其实是省略了细节。
  2. 有时包装您捕获的异常并提供更多有关它的详细信息(就像您在最后一行中打算做的那样)是有意义的,但再次确保您只是 添加细节,不漏细节

您可以阅读有关异常处理最佳实践的更多信息here

I would want to throw an exception with more information

通过使用自定义或 built-in 异常和 re-throwing.

来捕获异常以提供额外信息的做法没有错

我认为如果您只想对有限类型的异常执行此操作,方法是用 IllegalStateException 包装并添加自定义消息并保留异常原因。但是有一个 警告 OtherException 的类型不应该太宽泛,就像一般的 Exception,这样它只会包含有限数量的异常类型.

但是 re-throwing 只是一个原因 就像你对 Exception1Exception2 所做的那样看起来很可疑。出于某种原因,您不认为它们是 root-cases 并且会对它们可以携带的信息视而不见。这听起来像是一个设计缺陷。还要记住,原因可能是 null(在这种情况下,异常不会包装任何东西)并且此代码将失败并返回 NullPointerException。因此,在下面的示例中,相同的异常将得到 re-thrown 而不是 re-throwing 原因。

从clean-coding的角度来说,尽量少用type-checks比较好。我认为在这种情况下你不需要求助于 instanceof 检查。

相反,您可以利用异常处理机制提供的功能。

由于你需要执行相同的操作(re-throw捕获到的异常)如果发生Exception1Exception2,如果类型 Exception1Exception2siblings(即它们不会 extend 彼此),这些异常可以被处理使用 multi-catch clause 以避免代码重复:

catch (Exception1 | Exception2 e) {
    throw e;
}

否则,如果假设 Exception2Exception1 的子类型,那么您应该从更具体的类型开始在单独的 catch 块中处理它们,即 subtype 应该在 supertype:

之前处理
catch (Exception2 e) {
    throw e;
}
catch(Exception1 e) {
    throw e;
}

然后添加一个 catch 子句,其类型最少(比之前定义的所有异常类型更宽),以处理可能通过抛出 [= 的新实例而发生的所有其他异常类型13=].

示例:

public void myPublicMethod(Path source, Path destination) 
            throws FileAlreadyExistsException, DirectoryNotEmptyException {
    try {
        Files.copy(source, destination);
        // do some other actions
    }
    catch (FileAlreadyExistsException | DirectoryNotEmptyException e) {
        throw e;
    }
    catch (IOException e) {
        throw new IllegalStateException("some other type of exception occurred", e);
    }
}