【问题标题】:Exception handling, judgement and global error handlers in .Net.Net中的异常处理、判断和全局错误处理程序
【发布时间】:2011-07-04 23:01:18
【问题描述】:

在 .Net 中:

1) 您是否让异常传播到全局错误处理程序,您在其中记录错误并显示消息框?我问的原因是,如果您使用单个全局错误处理程序在顶部捕获异常,则需要一堆 If 来推断异常类型,然后显示可能友好的消息(例如,对于 filenotfound,您的文件未找到提供等)。

我的 winforms 应用程序中有一个全局错误处理程序。对于我无法处理的任何异常,他们会点击此处理程序,在该处理程序中记录异常并显示一条消息 - 但这是异常消息,有时我可能想要一些不同的东西,具体取决于受众等。

2) 我只捕获我可以处理的异常。但是,我的应用程序在用户可选择的路径中有一组文件夹。由于默认共享被锁定和审核等,任何丢失的文件都很少见,因此是“异常事件”。但是当用户在他或她自己的电脑上选择一个位置时,文件可能会丢失,目录名太长等。我认为这更常见,所以不要在这里使用异常。但之前我提到我做到了。对于这种冲突,是否有另一种方法来构造我的代码而不重复?

3) 最后,有一些我无法处理的异常(例如文件不存在,由于文件是特殊格式并且只能由某些 IT 人员上传,所以无法处理)。但这很少见。所以对于方法中的这些异常,我没有捕捉到它们。然而,当有人看到这一点时(尽管评论说这是在全球范围内捕获的),其他开发人员会不寒而栗。这是因为他们的无知吗?

最后,展开堆栈跟踪是什么意思?

谢谢 }

【问题讨论】:

  • 您专注于错误的功能。异常消息对用户几乎没有任何意义。只是 gobbledegook 他们单击“否”。该值在堆栈跟踪中,这对您来说意味着 一切

标签: .net exception exception-handling


【解决方案1】:

在某种程度上,您的问题取决于您的特定操作环境和语言(您暗示但从未透露),但在某种程度上,这些问题相当普遍。 (不幸的是,这也是一个存在很多分歧的领域,无论是在不同的操作环境之间还是在各个开发人员之间,有时在同一个项目中,所以我下面所说的可能需要根据您的上下文进行修改。)

从事异常工作 30 多年,我见过几乎所有异常机制和样式,包括一些非常原始的和一些过于“可爱”的。不过,有一些共同的原则:

首先,需要定义什么是异常。出于我们的目的,假设它是与当前执行的代码相关的异常情况的通知(而不是当前代码未使用的 I/O 设备上的情况)。假设它是相对于当前正在执行的代码同步交付的,而不是延迟(可能除了几个机器周期之外),也没有(最初)发送到不相关的代码。

继续这个思路,我们假设当前正在执行的程序有一个(至少是概念上的)调用返回堆栈,表示调用从程序执行开始到异常点的嵌套顺序。由于是这种情况,并且由于异常与当前执行的代码“相关”(例如,被零除是由于当前过程中的除法运算),因此同样存在与每个过程的排序关系调用返回堆栈。也就是说,每个过程都“知道”该异常直接或间接地由它自己的执行和它调用的过程引起。

这说明了什么?好吧,通常最接近错误的过程(即,位于调用返回堆栈“顶部”的那个)最“知道”错误的原因是什么,并且最适合处理它。但有时(例如,“找不到文件”异常)最接近异常点执行的过程没有足够的知识来处理错误(因为,例如,它不知道调用者在哪里得到了不存在的文件的名称,或者文件的内容应该代表什么)。出于这个原因,如果当前过程不能处理错误,它应该(隐式或显式)将错误“重新通知”给调用过程(依此类推,直到调用返回堆栈,直到找到想要处理错误的过程)。大多数异常方案会自动执行此“重新发出信号”,但有些异常方案需要程序员在一定程度上对其进行管理。

最终,如果错误未得到处理,您将到达调用返回堆栈的底部。这是默认处理程序接管并决定做什么(终止程序,在代码中的某个“安全”重新启动点恢复等)的时候。默认处理程序也可能会尝试记录错误(以及调用返回堆栈内容),以便稍后对其进行调试。

因此(由于您的特定操作环境没有任何理由),处理异常的最佳方法是在最“了解”它们的含义的各个过程中。在某些情况下,过程可能只知道一般意义上的异常意味着什么(例如,出于某种原因,除以零意味着“无效权重”),因此它可能会使用新的程序定义的异常类型重新发出信号,期望它的调用者(或其调用者的调用者......)将“知道”“无效重量”的含义以及如何处理它)。

许多人认为异常应该只用于处理“灾难性”错误,如果不是完全的程序终止,那么这些错误至少会给用户带来令人讨厌的错误消息,但这种观点是完全错误的。使用异常来管理无效的用户输入是非常好的,事实上,从“结构化编程”的角度来看,使用异常通常比使用返回码等更好。唯一需要注意的是,在某些环境中异常会非常缓慢,并且不应该在它们可能频繁发生的地方使用。但大多数较新的环境更加“开明”,在它们中可以使用异常而不用担心性能。

使用异常时确实需要稍微改变编程风格。最重要的是,必须注意构建代码,以便异常处理范围的开始/结束标记对应于与该可能异常相关的概念操作的开始和结束,并且必须确保任何“退出”代码发生异常时撤消部分完成的操作所需的操作适当地连接到该异常范围(例如,使用“finally”子句,或者,如果语言不支持,作为一个单独的子例程,可以从异常范围的异常处理程序)。但是一旦你开始理解这类事情,错误恢复就会变得非常非常简单。

在一般情况下,不处理无法处理的异常并让默认处理程序处理它们是完全可以的。然而,在某些情况下,可能需要关闭文件或释放资源。有时这可以通过默认处理程序完成,但在其他情况下,最好使用单独的异常处理程序(通常设置为捕获“任何”异常)来处理,这些处理程序只需执行所需的清理操作,然后重新发出原始异常的信号。异常允许您在代码中首先分配资源的位置附近执行此操作,因此默认处理程序不需要包含所有已分配资源的大列表并知道如何全部释放它们.这种技术可以显着减少由于资源未释放而导致的错误。

“展开堆栈(跟踪)”可能有几种不同的含义,但一般来说,当过程 A 中发生异常但通过调用返回堆栈返回到 A 的调用者 B 再到 B 的调用者 C 时,比如说,并且 C 的异常处理程序“处理”异常(不会将其重新发送给 C 的调用者),需要将调用返回堆栈恢复到 C 执行时的状态,以便 C 可以继续“正常” "(或多或少)执行。为此,堆栈上 A 和 B 的条目将(至少在概念上)“弹出”或“展开”。有时(取决于系统/语言)这种“展开”发生在未处理异常的“涟漪”发生时,但有时直到异常被标记为“已处理”才完成。通常你不需要关心这个,但一些系统或一些特殊情况可能需要了解它。

还需要注意的是,大多数系统都会在发生异常时以某种方式使调用返回堆栈的内容可用于记录为调试信息。这非常有用,尤其是在调试“远程”错误时,值得了解该机制在您的系统上是如何工作的。

【讨论】:

  • 很好的答案,谢谢。语言是 C#。重新关闭资源,我总是在 finally 块中执行此操作 - 无论如何。
【解决方案2】:

当且仅在以下情况下才应引发异常:(1) 例程承诺引发异常的条件存在,或 (2) 例程无法满足其承诺成功返回的后置条件.在许多情况下,可以足够松散地指定例程的后置条件,几乎可以保证它不会抛出,除非在堆栈溢出、线程中止或其他此类灾难的情况下,但在许多情况下它更有帮助提供更严格的保证。

例如,考虑从文件中读取“n”个字节的任务。可以编写一个例程从文件中读取多达“n”个字节,并返回读取的字节数(实际上,这将是没有异常的常用范例)。但是,尝试使用这样的例程解析文件可能会很烦人。最多读取 4 个字节,查看是否读取了 4 个字节,如果是,则将它们转换为长字节,最多读取该字节数,查看是否读取了所有预期字节等。每次读取后都必须进行测试以查看是否它成功地获得了所有预期的字节,很快就变成了一件苦差事,它掩盖了代码应该做什么。另一种方法是有一个例程承诺,只有当它成功读取所请求的字节数时才会返回,否则抛出异常。在这种情况下,字节读取例程中更严格的承诺消除了在代码中进行测试的需要,以确保读取完全且成功地完成。

异常的主要好处在于,如果大量函数调用中的任何一个未能满足预期的后置条件,就会导致相同的错误处理行为。如果在某个特定函数调用未按预期运行时需要具有特殊行为,则通常更方便的是让例程返回该行为是预期还是意外的指示,然后查看该结果。但是,如果一个人会进行数十次函数调用并且不会特别关心其中哪一个会产生意外行为,那么使用异常将避免对每一个进行错误检查的需要。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-06-02
    • 2011-04-07
    • 2014-04-17
    • 2012-12-31
    • 2011-05-19
    • 2011-08-31
    • 2010-12-05
    相关资源
    最近更新 更多