异常处理实践

10

无论如何,我有点困惑于何时传播异常和何时包装它以及它们之间的区别。

目前,我的理解告诉我,包装异常会涉及将类似于IO中的DriveNotFound异常并将其包装在一般的IOException中。

但是对于传播异常的概念,这只有在我有一个空catch子句时才会发生吗?因此,在ASP.NET Web应用程序中,它将传播到global.asax。或者在最近部署的Web应用程序中,未处理的HTTPException导致黄色屏幕死机并将日志写入Windows Server(这是我正在重写的Web应用程序)。因此,异常发生在方法中,可以在类级别处理,显示在页面上,然后上升到global.asax或Windows Server。

为什么我要用更通用的异常来包装一个异常呢?规则是使用最具体的类型处理异常(例如DriveNotFound显然是找不到驱动器)。此外,我该如何选择包装和替换异常?

异常处理链只是try和catch(或catches)子句吗?我认为从措辞中是的。

最后,为什么以及如何让异常向上调用堆栈传播?

我确实阅读了MS PandP关于异常处理的指南,但我想例子没有让我充分理解一切。

这个问题来自于Enterprise Library的包装/传播异常等能力。我对传播感到不确定,并且替换/包装异常的区别。

此外,在catch块中插入复杂的错误处理逻辑(例如ifs/elses之类的)是否可以?

谢谢

5个回答

11

不少于6个问题 :-)

但是关于传播异常的概念,这只会在有空catch子句的情况下发生吗?

异常将向上传播,直到被更高层次的调用堆栈中处理该特定异常类型或更接近该异常层次结构基类型的异常类型的catch块捕获。例如,所有托管异常都从System.Exception派生,因此拦截System.Exception的catch块将捕获每个托管异常。

为什么我要用更通用的异常来包装异常?

我不确定你指的是什么。您是否意味着捕获异常,用另一个替换它,并将原始异常添加为新异常的InnerException属性?还是其他事情?

我认为很少有很好的理由使用更通用的异常来替换异常。但是,您可以确实将异常替换为另一个异常,出于以下三个原因之一或多个原因:

  • 隐藏对调用者的实现细节。
  • 提高抽象级别,使其对调用者更有意义。
  • 抛出与处理的问题非常相关的自定义异常。

此外,我如何在包装和替换异常之间进行选择?

对不起,但我仍然不理解您如何将这两者定义为不同的事情。

异常处理链只是try和catch(或catches)子句吗?

以下是抛出异常时发生的基本情况:

  • CLR顺序遍历局部Try...End Try块内的Catch块列表,寻找具有匹配抛出的异常的异常筛选器的本地Catch块。

  • 如果本地Catch块具有与抛出的精确异常匹配的异常过滤器,则该Catch块中的代码将被执行,然后执行Finally块中的代码。然后继续执行End Try后的第一条语句。

  • 或者,如果抛出的异常派生自本地Catch块指定的异常,则会发生与第二步描述的相同的操作。例如,捕获ArgumentException的异常过滤器也会捕获派生自ArgumentException的异常,例如ArgumentNullException、InvalidEnumArgumentException、DuplicateWaitObjectException和ArgumentOutOfRangeException。

  • 如果没有本地Catch块与抛出的异常匹配,则CLR沿调用堆栈向上走,逐个查找要响应异常的Catch块。如果在调用堆栈中找不到匹配的Catch块,则认为该异常未处理。

  • 或者,如果在调用堆栈中的某个位置找到了匹配的Catch块,则将执行throw和catch之间的每个Finally块中的代码。这始于抛出异常所在的Try块的Finally,并在捕获异常的方法下面的方法的Finally结束。

  • 在完成所有捕获异常下面的方法的清理之后,控制转移到捕获异常的Catch块,并执行此代码。接下来运行的是捕获异常的Try的Finally块。现在,由于调用堆栈已被展开并完成了错误清理,因此最后一步是继续执行捕获异常的End Try后的第一条语句。

  • 如果Catch块中的代码导致抛出另一个异常,则使用InnerException属性将原始异常自动附加到新异常上。通过这种方式,异常可以堆叠而不会丢失任何信息。

  • 您应避免将可能引发异常的清理代码放置在Finally块中,除非该代码位于其自己的Try块内。如果没有添加此额外的保护,则CLR会表现得好像新异常是在Finally块结束后抛出的,并查找远程Catch块以响应新异常。除非原始Catch块保存它,否则原始异常将丢失。

  • 最后,我为什么要让异常向调用栈上传播?

    为什么:当你不明确了解异常并知道如何从中恢复时,应该让它向上传播。

    如何:只捕获你理解并知道如何处理的异常类型。偶尔需要异常的详细信息才能进行适当的恢复。在这种情况下,您可以捕获它,执行恢复操作,然后使用throw;语句重新抛出它。

    此外,将复杂的错误处理逻辑插入catch块中是否可行(例如if/else之类的)。

    一般来说,可以,因为你的Catch块中的代码引发的任何新异常都会自动通过InnerException属性附加到旧异常上。但是,如果有可能避免触发此机制,最好不要挑衅它,所以你的代码越简单,越好。保持Catch代码简单的另一个很好的原因是,通常它不会经过与主线代码相同的测试程度。


    5

    重要的是向您的呼叫者传达正确的含义。我怀疑有没有必要将特定的异常包装在更通用的异常中 - 这对任何人都没有帮助。

    考虑一个与文件访问无关但在幕后访问配置文件的API。如果配置文件不存在,则可以将 FileNotFoundException 包装在 ConfigurationException 中,以便向呼叫者传达正确的问题。

    为什么以及如何让异常上升到调用堆栈?

    如果您无法处理异常,则可以让异常上升。就这么简单。如果您的代码没有或不应该围绕异常进行操作,则让它上升。在传播时,请注意如何传播:

    throw ex;
    

    与之不同的是:

    throw;
    

    前者会丢弃旧的堆栈跟踪并从抛出异常的点创建另一个。后者保留原始堆栈跟踪。

    当然,如果您无法处理异常,则可能首先不必捕获它(也许您想记录日志)。


    5

    4

    通常我这样做:

    • 业务逻辑组件(dll)仅在完全理解时处理异常
    • 预期但无法处理的异常会被封装成单个异常类型并允许向上调用堆栈传播(即数据库交互期间可能发生的所有不同异常均被包装在单个RepositoryException中并抛出)
    • 不要在dll中包含catch(Exception ex)
    • 只在最后一刻处理异常(即UI控制器)。

    通常情况下,只有在调用堆栈中较高层级时才能确切地知道自己正在做什么(当前的业务流程是什么)以及如何正确处理异常。


    0

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接