为什么要重新抛出异常?

时间:2008-09-22 07:11:17

标签: language-agnostic exception exception-handling

我多次见过以下代码:

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

    // or
    // throw;

    // or
    // throw ex;
}

您能解释重新抛出异常的目的吗?它是否遵循异常处理中的模式/最佳实践? (我在某处读过它被称为“来电通知”的模式?)

13 个答案:

答案 0 :(得分:38)

如果您想要记录异常但不处理异常,则重新抛出相同的异常非常有用。

抛出一个包装被捕获异常的新异常有利于抽象。例如,您的库使用第三方库,该库会引发您的库的客户端不应该知道的异常。在这种情况下,您将它包装到一个更为本机库的异常类型中,然后抛出它。

答案 1 :(得分:24)

实际上

之间存在差异
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);
    } 
}

答案 2 :(得分:10)

有时您希望隐藏方法的实现细节或改进 问题的抽象级别,以便它对调用者更有意义 一种方法。为此,您可以拦截原始异常并替换 一个更适合解释问题的自定义异常。

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

与您显示的代码不同,最佳实践是应该通过将其作为新异常的InnerException属性加载来保留原始异常。这意味着开发人员仍然可以在必要时分析基础问题。

当您创建自定义异常时,这是一个有用的核对清单:

•找到一个好名称,说明抛出异常的原因,并确保名称以“Exception”结尾。

•确保实现三个标准异常构造函数。

•确保使用Serializable属性标记例外。

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

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

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

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

•请记住使用自定义异常的InnerException属性附加原始异常。

答案 3 :(得分:8)

您通常会出于以下两个原因之一捕获并重新抛出,具体取决于代码在架构中的位置。

在应用程序的核心,您通常会抓住并重新抛出异常,将异常翻译成更有意义的东西。例如,如果您正在编写数据访问层并使用SQL Server自定义错误代码,则可能会将SqlException转换为ObjectNotFoundException之类的内容。这很有用,因为(a)它使调用者更容易处理特定类型的异常,并且(b)因为它阻止了该层的实现细节,例如您使用SQL Server将持久性泄漏到其他层的事实,允许您更轻松地更改未来的事物。

在应用程序的边界处,在不转换异常的情况下捕获和重新抛出是很常见的,这样您就可以记录它的详细信息,帮助调试和诊断实时问题。理想情况下,您希望在操作团队可以轻松监视的某个位置(例如,事件日志)发布错误,以及在开发人员的控制流中发生异常的位置(通常是跟踪)的某处。

答案 4 :(得分:2)

我可以想到以下原因:

  • 保持抛出的异常类型集合作为API的一部分,以便调用者只需担心固定的异常集。在Java中,由于经过检查的异常机制,您实际上不得不这样做。

  • 向异常添加一些上下文信息。例如,您可能希望捕获它并添加“...处理订单没有XXX,寻找产品YYY”,而不是让“未找到记录”从DB中传出。

  • 进行一些清理 - 关闭文件,回滚事务,释放一些句柄。

答案 5 :(得分:1)

通常,“Do Something”要么更好地解释异常(例如,将其包装在另一个异常中),要么通过某个来源追踪信息。

另一种可能性是,如果异常类型不足以知道是否需要捕获异常,在这种情况下捕获它将提供更多信息。

这并不是说该方法的使用纯粹是出于好的原因,很多时候,当开发人员认为在将来某个时候可能需要跟踪信息时会使用该方法,在这种情况下,你会得到try {} catch {throw;} style ,这根本没用。

答案 6 :(得分:1)

我认为这取决于您尝试对异常做什么。

一个很好的理由是首先在catch中记录错误,然后将其抛出到UI以生成友好的错误消息,并选择查看错误的更多“高级/详细”视图,其中包含原始错误。

另一种方法是“重试”方法,例如,保留错误计数,并且在一定次数的重试之后,这是错误被发送到堆栈的唯一时间(这有时是针对数据库调用的数据库访问而进行的)超时,或通过慢速网络访问Web服务。)

虽然会有很多其他原因。

答案 7 :(得分:1)

仅供参考,这是关于每种类型的重新投掷的相关问题: Performance Considerations for throwing Exceptions

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

答案 8 :(得分:0)

在我开始使用EntLib ExceptionBlock之前,我在使用它们之前使用它们来记录错误。当你认为我可以在那时处理它们时有点讨厌,但当时最好让它们在UAT中(在记录它们之后)失败,而不是掩盖流程错误。

答案 9 :(得分:0)

应用程序很可能会在调用堆栈的上方捕获那些重新抛出的异常,因此重新抛出它们允许更高级别的处理程序在适当时拦截和处理它们。应用程序通常具有记录或报告预期的顶级异常处理程序。

另一种选择是编码器是懒惰的,而不只是捕捉他们想要处理的一组例外,他们已经抓住了所有东西,然后只重新抛出他们无法实际处理的那些。

答案 10 :(得分:0)

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

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

答案 11 :(得分:0)

如果你将异常视为获取方法结果的另一种方法,那么重新抛出异常就像将结果包装到其他对象中一样。

这是一个非特殊世界的常见操作。通常这发生在两个应用程序层的边界上 - 当层B中的函数调用层C中的函数时,它会将C的结果转换为B的内部形式。

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

如果没有,那么在调用图层A的图层B处将有一整套JDK例外要处理。 : - )

正如接受的答案所指出的,图层A可能甚至不知道C的例外情况。

示例

第A层,servlet:检索图像及其元信息
B层,JPEG库:收集可用的DCIM标签来解析JPEG文件
Layer C ,一个简单的DB:从随机访问文件中读取字符串的类。一些字节被破坏,因此它抛出一个异常,说“无法读取UTF-8字符串用于记录'bibliographicCitation'”。

所以A无法理解'bibliographicCitation'的含义。因此,B应将A的此例外转换为包裹原始文件的TagsReadingException

答案 12 :(得分:-3)

重新抛出异常的主要原因是保持Call Stack不受影响,因此您可以更全面地了解发生的情况并调用序列。