为什么重新再扔例外?
-
02-07-2019 - |
题
我已经看到了下列代码多次:
try
{
... // some code
}
catch (Exception ex)
{
... // Do something
throw new CustomException(ex);
// or
// throw;
// or
// throw ex;
}
能否请你解释的目的重新投掷的一个例外吗?它是下列模式/最佳实践中的例外处理呢?(我已经读的地方,这就是所谓的"主叫方通知"图案?)
解决方案
如果您想要记录异常但不处理异常,则重新抛出相同的异常非常有用。
抛出一个包装被捕获异常的新异常有利于抽象。例如,您的库使用第三方库,该库会引发您的库的客户端不应该知道的异常。在这种情况下,您将它包装到一个更为本机库的异常类型中,然后抛出它。
其他提示
实际上
之间存在差异throw new CustomException(ex);
和
throw;
第二个将保留堆栈信息。
但有时你想让Exception更“友好”。在您的应用程序域中,您将提出包含原始异常的自定义异常,而不是让DatabaseException到达您的GUI。
例如:
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);
}
}
有时您希望隐藏方法的实现细节或改进 问题的抽象程度,使其对调用者更有意义 一种方法。为此,您可以拦截原始异常并替换 一个自定义的例外,它更适合解释问题。
以一个从文本文件加载所请求用户详细信息的方法为例。该方法假定存在一个文本文件,其名称带有用户ID和后缀“ .data”。当该文件实际上不存在时,抛出FileNotFoundException没有多大意义,因为每个用户的详细信息存储在文本文件中的事实是该方法内部的实现细节。因此,此方法可以使用解释性消息将原始异常包装在自定义异常中。
与您显示的代码不同,最佳实践是应该通过将其作为新异常的InnerException属性加载来保留原始异常。这意味着开发人员仍然可以在必要时分析基础问题。
当您创建自定义异常时,这是一个有用的核对清单:
•找到一个好的名称,它传达了抛出异常的原因,并确保名称以““ Exception””字样结束。
•确保实现三个标准异常构造函数。
•确保使用Serializable属性标记您的异常。
•确保实现反序列化构造函数。
•添加任何可能有助于开发人员更好地理解和处理异常的自定义异常属性。
•如果添加任何自定义属性,请确保实现并覆盖GetObjectData以序列化自定义属性。
•如果添加任何自定义属性,请覆盖Message属性,以便将属性添加到标准异常消息中。
•请记住使用自定义异常的InnerException属性附加原始异常。
您通常会出于以下两个原因之一捕获并重新抛出,具体取决于代码在架构中的位置。
在应用程序的核心,您通常会抓住并重新抛出异常,将异常翻译成更有意义的东西。例如,如果您正在编写数据访问层并使用SQL Server自定义错误代码,则可能会将SqlException转换为ObjectNotFoundException之类的内容。这很有用,因为(a)它使调用者更容易处理特定类型的异常,并且(b)因为它阻止了该层的实现细节,例如你使用SQL Server将持久性泄漏到其他层的事实,允许您更轻松地更改未来的事物。
在应用程序的边界处,在不转换异常的情况下捕获和重新抛出是很常见的,这样您就可以记录它的详细信息,帮助调试和诊断实时问题。理想情况下,您希望在操作团队可以轻松监视的某个位置(例如,事件日志)发布错误,以及在开发人员控制流中发生异常的位置(通常是跟踪)的某处。
我能想到的原因如下:
保持设置的引发的异常类型固定的,为API的一部分,这样的呼叫者只需要担心的固定的例外情况。在爪哇,你实际上是被迫这样做,因为检查的例外情况的机制。
加入一些方面信息的例外。例如,而不是让裸"的记录中找不到"通过数据库,您可能想要抓住它,并添加"...虽然处理了没有XXX,寻找产品YYY"。
做一些清理关闭文件,滚回交易,释放一些处理。
一般来说,“做某事”要么更好地解释异常(例如,将其包装在另一个异常中),要么通过某个来源追踪信息。
另一种可能性是,如果异常类型不足以知道是否需要捕获异常,在这种情况下捕获它将提供更多信息。
这并不是说该方法的使用纯粹是出于好的原因,很多时候,当开发人员认为在将来某个时候可能需要跟踪信息时会使用该方法,在这种情况下,你会得到try {} catch {throw;} style ,这根本没用。
我认为这取决于您尝试对异常做什么。
一个很好的理由是首先在catch中记录错误,然后将其抛出到UI以生成友好的错误消息,并且可以选择查看更多“高级/详细”消息。查看错误,其中包含原始错误。
另一种方法是“重试”。方法,例如,保留错误计数,并且在一定量的重试之后,这是错误被发送到堆栈的唯一时间(对于超时的数据库调用或通过慢速网络访问Web服务,有时会对数据库访问进行此操作)。
虽然会有很多其他原因。
仅供参考,这是关于每种类型的重新投掷的相关问题: 投掷例外的效果注意事项
我的问题集中于“为什么”?我们在应用程序异常处理策略中重新抛出异常及其用法。
在我开始使用EntLib ExceptionBlock之前,我在使用它们之前使用它们来记录错误。当你认为我可以在那时处理它们时,有点讨厌,但当时最好让它们在UAT中(在记录它们之后)失败,而不是掩盖流程错误。
应用程序很可能会在调用堆栈的上方捕获那些重新抛出的异常,因此重新抛出它们允许更高级别的处理程序在适当时拦截和处理它们。应用程序通常具有记录或报告预期的顶级异常处理程序。
另一种选择是编码器是懒惰的,而不是只捕获他们想要处理的异常集,而是抓住了所有内容,然后只重新抛出了他们无法实际处理的异常。
正如Rafal所提到的,有时这样做是为了将已检查的异常转换为未经检查的异常,或转换为更适合API的已检查异常。这里有一个例子:
http://radio-weblogs.com/0122027 /stories/2003/04/01/JavasCheckedExceptionsWereAMistake.html
如果你将异常视为获取方法结果的另一种方法,那么重新抛出异常就像将结果包装到其他对象中一样。
这是一个非特殊世界的常见操作。通常这发生在两个应用层的边界上 - 当层 B
中的函数从层 C
调用一个函数时,它会转换 C
's结果进入 B
的内部形式。
A - 来电 - > B - 通话 - > ç代码>
如果没有,那么在调用图层 B
的图层 A
处,将会有一整套JDK例外要处理。 : - )
正如接受的答案所指出的那样,图层 A
甚至可能都不知道 C
的例外。
示例强>
第A层,servlet:检索图像及其元信息
Layer B ,JPEG库:收集可用的DCIM标签来解析JPEG文件
Layer C ,一个简单的DB:从随机访问文件中读取字符串的类。一些字节被破坏,因此它抛出一个异常,说“不能读取UTF-8字符串用于记录'bibliographicCitation'”。
所以 A
不会理解'bibliographicCitation'的含义。所以 B
应该将 A
的这个异常翻译成包装原文的 TagsReadingException
。
重新抛出异常的主要原因是保持Call Stack不受影响,因此您可以更全面地了解发生的情况并调用序列。