C#中的常规异常处理

9

我正在通过FxCop运行一些代码,并且目前正在清除所有非强制性违规。

代码本身有一些try/catch块,只是捕获常规异常;

try
{
    // Some code in here that could throw an exception
}
catch(Exception ex)
{
    // Exception Thrown ... sort it out!
}

现在我们都知道这是不好的实践,但我认为我知道如何正确地做到这一点,但 FxCop 有其他想法!
假设 try 块中的代码可能会抛出一个 IO 异常,而且仅限于 IO 异常。像这样做应该没有任何问题:
try
{
    // Code in here that can only throw an IOException
}
catch (System.IO.IOException ioExp)
{
    // Handle the IO exception or throw it
}
catch (System.Exception ex)
{
    // Catch otherwise unhandled exception
}

但是FxCop不同意我的观点...它仍然将此标记为违规,因为我捕获了System.Exception。这真的是不好的实践吗?还是说我可以安全地忽略这个违规?

1
这要看情况...在“// 捕获未处理的异常”中你在做什么? - Heinzi
该应用程序可以向我们的 Helpdesk 发送电子邮件 - 该邮件将传递给开发人员 - 因此我们可能会在其中触发一个包含堆栈跟踪的邮件。 - DilbertDave
你确定代码块不会抛出OutOfMemoryException或其他致命错误吗? - SoftMemes
以上是假设情况 - 实际代码中的方法调用可能会抛出8种不同的异常之一。但是,您能确定它们是唯一可能发生的异常吗?因此有这个问题;-) - DilbertDave
8个回答

7
我同意FxCop和其他大多数答案的观点:永远不要捕获System.Exception,除非在应用程序的最高级别上记录意外(因此是致命的)异常并使应用程序崩溃。 此外,也请查看与此相关的问题。 一些其他有效的异常可能包括:
  • 为了重新抛出更详细的异常而捕获异常。
  • 在需要比ExpectedExceptionAttribute更复杂的单元测试代码中。
  • 在门面库代码中,该库仅存在于保护调用者免受调用某些外部(Web)服务的复杂性的影响,而自身从不失败,无论外部系统崩溃得有多么严重。

如果您想记录较低级别方法参数的某些值时发生的异常,该怎么办?仅从“应用程序中最高级别”是无法实现的。 - Swanny
@Swanny: 对于一般情况下的Exception.Data怎么样?或者带有额外属性的自定义异常类型呢?都不是问题。你还可以在拥有该数据访问权限的较低级别处捕捉异常,必要时记录日志,并将异常封装成更具描述性的(自定义)异常类型进行抛出。因此,这也不是问题,对吧? - peSHIr
你仍然需要填充数据集合,因此你必须捕获异常来完成这个任务,所以我们又有了另一个永远不要出现的异常。这里又有一个例子:假设我正在处理一个包含独立记录的大批量文件,并且在处理记录时遇到了意外的异常。我应该将这个意外的异常抛出到“最高级别”吗?不,我会将其记录下来并继续处理下一条记录,直到我到达文件末尾或达到允许的最大错误记录数,以先到者为准。 - Swanny
请注意,如果你只是记录并重新抛出异常,那没问题。重点是你的代码可能不知道如何处理非特定异常。 - TrueWill

5
我同意其他答案的观点,如果你只是记录并重新抛出异常或者在应用程序的最高级别(UI)中执行操作,那么catch (Exception ex)是可以的。
还有一种情况让我想起,在编写无人值守代码时(例如某些系统服务),如果某个(明确定义的)子任务由于任何原因而失败,保持程序运行是完全有道理的。当然,必须注意确保问题的原因被记录下来,并且(也许)在一段时间后再次尝试该任务。

4
如果您正在编写库(框架),那么FxCop是100%正确的。
捕获异常 - 这意味着什么?这意味着您知道它们被抛出的原因。您确定您知道所有可能的原因吗?
如果您只是编写应用程序,则可能会有不同情况。例如,如果您捕获所有未处理的异常以进行日志记录。

我敢肯定你永远无法知道所有的原因,但是从try块内的方法调用来看,我知道当前的代码至少可能会抛出8种不同的异常类型。 - DilbertDave
你必须单独处理所有的异常!一个异常可能是因为无效的文件名,另一个可能是因为磁盘IO错误。你应该分别回应这些异常,因为这将与用户进行沟通。 或者,如果FxCop对你来说很无聊,可以忽略它 :-)。 - Ilya Khaprov
但是,如果我只是从每个catch块中“返回false;”,那似乎毫无意义。 我们刚刚开始使用诸如FxCop之类的代码分析工具,需要审查我们对它们的使用。 - DilbertDave
1
是的,就像在我们有TryParse之前,判断一个字符串是否为整数的唯一简单方法(从代码角度来看)就是调用parse,返回true或捕获异常并返回false。这里唯一的真正问题是,对于正常情况捕获异常可能会有点慢,如果在循环密集的情况下发生,这可能会导致您的应用程序变慢。 - Swanny
1
@Swanny 这就是为什么添加了 TryParse,因为:1)当你捕获异常只是为了检查数据格式是否正确时,这不是一个好的设计选择;2)异常很慢。 - Ilya Khaprov
是的,但不是那么慢。对于标准数据输入验证,它已经完美地通过了验证。从来没有人向我抱怨“哎呀,这个响应时间慢了1毫秒”。如果必须解析一百万或更多的值,并且有很大的可能性这些值是错误的,我会重新考虑(也许首先检查长度和字符混合)。现在我使用TryParse。请记住,违规行为是关于不试图隐藏意外运行时错误,而不是关于性能。对于类库,是的,我可能只想记录所有异常以及导致异常的参数值。 - Swanny

4

灰色地带...

在理想的情况下,您应该始终捕获明确的异常,因为从理论上讲,这些是您可以合理处理的唯一类型 - 任何其他类型都应该通过某个顶级处理程序传递。

问题在于,我们不一定生活在一个理想的世界,并且很可能想要使用通用处理程序将有关通用异常(参数等)的附加信息累积到异常中和/或在将异常传递回树之前执行其他日志记录,并在适当的级别上 - 安排好事情,以便我们的最终用户在UI中看不到原始异常。对此的反驳是建议在低级别发生新错误时(而不是到达您的应用程序级处理程序),然后添加特定于异常的处理程序,可以为您进行捕获 - 但是可能存在部署问题,因为其中某些代码位包含通用情况,这些情况比您想象的更容易出现未处理的异常。

如果是我,我可能会考虑按程序集的标志来处理...

p.s. 我假设您没有一个仅吞下异常并允许应用程序继续运行的处理程序。


等一下。你只捕获你预期的异常。那么意外的异常会导致程序崩溃?这不是我所谓的专业解决方案。看看为WinForms引入的通用异常处理(1.1或2.0)。如果我需要在文件中处理100万个独立记录,我宁愿捕获意外的异常,将错误记录在日志中并记录记录号,然后继续处理下一个记录,可能还有一个允许的最大坏记录数。 - Swanny
额,我绝对没有建议意外异常应该使您的程序崩溃。参见“顶级处理程序”-您提出的特定情况也是一个非常好的观点,因此需要进一步观察并根据具体情况进行处理。 - Murph
我认为,最接近这种语言精神的答案是,如果开发人员无论如何都要在顶层处理错误,那么语言就不应该干涉。这非常实用主义而非纯粹主义。安德斯曾经接受采访时被问到检查与未检查的问题,他的回答是这样的。 - CurtainDog

3

你应该吞掉你知道如何处理的异常。捕获异常并不抛出它(直接或包装在特定异常中)意味着你知道应用程序上下文中异常的具体情况。由于异常可以是任何东西,你可能不知道如何处理它。

如果你只是记录异常,那没什么大不了的,只需记录它并将其抛出给外层进行处理或捕获即可。


当然,如果外层没有预料到异常,那么它会如何处理异常呢? - Swanny
通常情况下,它不会对其进行任何操作,但是最后一道防线的全局异常处理程序可以捕获它,向用户提供反馈并防止应用程序关闭。这不是类库的负担,而是主机的责任。 - Yann Schwartz
这听起来像是处理意外异常的完美方式。但您是否会接受有些情况下类库不会抛出意外异常,比如我在本页上已经多次提到的独立记录批处理文件。 - Swanny

2
FxCopy说具体的违规行为是什么?如果你只想记录有关异常的信息,然后重新抛出它,那么这是完全有效的,尽管还有其他方法可以完成此操作。但如果您要重新引发异常,请确保仅使用以下内容:
 throw;

而不是。
 throw ex;

也许这就是问题所在?

1
有几个实例是这样的,但被标记为RethrowToPreserveStackDetails违规。 - DilbertDave
好的。那就使用 FxCop 吧。你捕获 System.Exception 时收到的违规名称是什么? - Swanny
问题中涉及的违规是 DoNotCatchGeneralExceptionTypes:http://msdn2.microsoft.com/library/ms182137(VS.90).aspx - DilbertDave
1
好的,现在明白了。他们不希望你隐藏运行时异常的细节。听起来像是有人过于关注前列腺了。考虑这一点,如果不适用,就继续前进。我已经因为完全合理的原因多次捕获了System.Exception,并且有很多明显的情况需要这样做。 - Swanny

2

如果代码块只应该抛出IOException异常,那么为什么还要捕获System.Exception呢?

然后,您想实现与FxCop指南的符合程度是您的选择。例如,我认为在最高级别上捕获每个异常并记录所有可能的信息是没有问题的。


2
您应该只捕获您可以处理的异常。否则,让异常向上传递给调用者,或者如果在 Gui 应用程序中,异常应该由通用 catch-all 处理程序处理,然后记录下来。参见:UnhandledException。因此,除非您打算对其进行某些操作,否则不要捕获异常。不可否认,这可能仅仅是记录错误。
编辑:还请查看 Application.ThreadException

是的 - 但是除非你在某个时候捕获所有异常,否则它会作为未处理的异常到达UI。 - DilbertDave
@DilbertDave:就我而言,这应该是永远不捕获“System.Exception”规则的唯一例外。 - peSHIr
@DilbertDave,这不是为什么要为UnhandledException事件分配处理程序吗? - Dog Ears
实际上,WinForm全局异常处理得到了改进,以便您可以在GUI线程上捕获所有异常,与其他线程分开。对于大多数应用程序来说,这是相同的事情。我通常有一个公共对话框,显示类似于“很抱歉,由于以下意外错误,上次操作无法完成:”+ ex.Message的内容。一个按钮将详细信息发送到服务台。另一个关闭对话框,让用户有机会纠正错误并重试,或者至少在关闭之前复制他们输入的详细信息。而不仅仅是崩溃。 - Swanny
我应该说一下,这是一个ASP.NET应用程序。 - DilbertDave

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