为什么重新抛出exception?

我多次看过下面的代码:

try { ... // some code } catch (Exception ex) { ... // Do something throw new CustomException(ex); // or // throw; // or // throw ex; } 

你能解释重新抛出exception的目的吗? 它遵循exception处理中的模式/最佳实践吗? (我读过的地方叫做“呼叫者通知”模式?)

如果你想logging这个exception,但不处理它,那么重试这个exception就很有用。

抛出一个包含被捕获的exception的新exception对于抽象是有好处的。 例如,您的图书馆使用第三方图书馆,引发图书馆的客户不应该知道的exception。 在这种情况下,将其包装成更为本机的exceptiontypes,然后抛出。

其实是有区别的

 throw new CustomException(ex); 

 throw; 

第二个将保存堆栈信息。

但是有时候你想让exception对你的应用程序域更加“友好”,而不是让DatabaseException到达你的GUI,你将会抛出包含原始exception的自定义exception。

例如:

 try { } catch (SqlException ex) { switch (ex.Number) { case 17: case 4060: case 18456: throw new InvalidDatabaseConnectionException("The database does not exists or cannot be reached using the supplied connection settings.", ex); case 547: throw new CouldNotDeleteException("There is a another object still using this object, therefore it cannot be deleted.", ex); default: throw new UnexpectedDatabaseErrorException("There was an unexpected error from the database.", ex); } } 

有时候你想隐藏一个方法的实现细节,或者提高一个问题的抽象级别,这样对于一个方法的调用者来说更有意义。 要做到这一点,你可以拦截原来的exception,并replace一个更适合于解释问题的自定义exception。

以一个从文本文件加载请求的用户细节的方法为例。 该方法假定存在以用户ID和后缀“.data”命名的文本文件。 当该文件实际上不存在时,抛出FileNotFoundException没有多大意义,因为每个用户的详细信息存储在文本文件中的事实是该方法内部的实现细节。 所以这个方法可以用一个解释性的消息把原来的exception封装在一个自定义exception中。

与显示的代码不同,最佳做法是通过将原始exception加载为新exception的InnerException属性来保留原始exception。 这意味着如果需要,开发人员仍然可以分析潜在的问题。

在创build自定义exception时,以下是一个有用的清单:

•find一个好名字,传达为什么抛出exception,并确保名称以“Exception”结尾。

确保你实现了三个标准的exception构造函数。

•确保使用Serializable属性标记exception。

确保您实现了反序列化构造函数。

•添加可能帮助开发人员更好地理解和处理exception的任何自定义exception属性。

•如果您添加任何自定义属性,请确保您实现并重写GetObjectData以序列化您的自定义属性。

•如果您添加任何自定义属性,请覆盖Message属性,以便您可以将属性添加到标准exception消息。

•记住使用自定义exception的InnerException属性来附加原始exception。

根据代码在应用程序体系结构中的位置,您通常会捕获并重新抛出两个原因之一。

在一个应用程序的核心,你通常会捕获并重新抛出exception,将其转化为更有意义的东西。 例如,如果您正在编写数据访问层并在SQL Server中使用自定义错误代码,则可能会将SqlException转换为像ObjectNotFoundException之类的内容。 这很有用,因为(a)它使调用者更容易处理特定types的exception;(b)因为它阻止了该层的实现细节,例如使用SQL Server进行持久性泄露到其他层的事实可以让你更容易地改变未来的事物。

在应用程序边界处,通常会捕获并重新抛出,而不会转换exception,以便logging其详细信息,帮助debugging和诊断实时问题。 理想情况下,您希望在操作团队可以轻松监视的地方(例如事件日志)发布错误,并在某处为开发人员(通常是跟踪)提供控制stream中发生exception的上下文。

我可以想到以下原因:

  • 作为API的一部分,保留抛出的exceptiontypes集,以便调用者只需要担心固定的exception集。 在Java中,由于检查的exception机制,你实际上被迫这样做。

  • 将一些上下文信息添加到例外。 例如,不要让数据库中传递裸露的“未findlogging”,您可能想要捕获并添加“…处理订单号XXX,查找产品YYY”。

  • 做一些清理 – closures文件,回滚事务,释放一些句柄。

一般而言,“做某事”要么更好地解释exception(例如,将其封装在另一个例外中),要么通过某个源来跟踪信息。

另一种可能是,如果exceptiontypes没有足够的信息来知道是否需要捕获exception,在这种情况下捕获exception将提供更多的信息。

这并不是说这个方法纯粹是出于好的理由,当开发人员认为在将来某个时候可能需要跟踪信息时,很多时候会使用这种方法,在这种情况下,您可以尝试{catch} {throw;} style,这是没有任何帮助。

我认为这取决于你正在试图做的例外。

一个很好的理由是在错误中首先logging错误,然后将错误信息放到UI中,生成一个友好的错误消息,其中包含原始错误的更多“高级/详细”视图。

另一种方法是“重试”方法,例如保持错误计数,并且在一定次数的重试之后,这是错误被发送到堆栈的唯一时间(这有时是针对超时的数据库访问的数据库访问完成的,或者通过慢速networking访问networking服务)。

还有其他一些理由可以做到这一点。

仅供参考,这是一个关于每种types重新抛出的相关问题: 抛出exception的性能注意事项

我的问题集中在“为什么”我们重新抛出exception及其在应用程序exception处理策略中的用法。

在我开始使用EntLib ExceptionBlock之前,我正在使用它们来logging错误,然后抛出它们。 当你认为我可以在那个时候处理它们的时候有点讨厌,但是当时最好让它们在UAT(在logging它们之后)中快速地失败,而不是覆盖一个stream动的错误。

应用程序很可能会捕捉那些调用堆栈中更高的重新抛出的exception,因此重新抛出它们允许更高级的处理程序在适当的时候拦截并处理它们。 应用程序拥有一个logging或报告期望的顶级exception处理程序是很常见的。

另一种select是编码器是懒惰的,而不是捕捉他们想要处理的一组exception,他们抓住了一切,然后重新抛出只有他们实际上不能处理的exception。

正如Rafal所提到的,有时候这样做是为了将检查的exception转换为未经检查的exception,或者转换为更适合于API的检查exception。 这里有一个例子:

http://radio-weblogs.com/0122027/stories/2003/04/01/JavasCheckedExceptionsWereAMistake.html

如果以另一种方式查看exception来获取方法结果 ,那么重新抛出exception就像将结果封装到其他对象中一样。

这是一个非常规的世界中的常见操作。 通常这发生在两个应用程序层的边界上 – 当B层的函数调用C层的函数时,它将C的结果转换成B的内部forms。

A -- calls --> B -- calls --> C

如果没有,那么在调用层B的层B将会有一整套JDKexception来处理。 🙂

正如接受的答案指出的那样, A层甚至可能不知道C的例外。

图层A ,servlet:检索图像,它是元信息
B层 ,JPEG库:收集可用的DCIM标签来parsingJPEG文件
层C ,一个简单的DB:一个从一个随机访问文件中读取string的类。 一些字节被破坏,所以它会抛出一个exception,说“不能读取UTF-8string的logging”书目引用“”。

所以A不理解“书目引文”的含义。 所以B应该把这个例外转换成包装原始的TagsReadingException

重抛exception的主要原因是保持调用堆栈不变,这样可以更全面地了解发生的情况并调用序列。