在C#中,我该如何知道要捕获哪些异常?

11

我已经养成了使用一般性catch语句并以一般方式处理这些异常的习惯。这是不好的实践吗?如果是,那么我怎么知道哪些特定的异常可能会被抛出,哪些异常需要被捕获?


1
我希望VS2008/10有一个“分析模式”,可以强制你处理所有的异常。 - Hamish Grubijan
@Hamish:你可以尝试制作一个扩展。不过,未经检查的异常是一个非常有意识的设计选择,基于从Java中学到的教训。 - Greg D
@Greg D,我该如何实现这个? - Hamish Grubijan
11个回答

19
  1. 是的,除了一些非常特定的情况,这是个不好的做法。我知道的唯一一个普遍适用的情况是,在应用程序即将关闭之前记录消息或堆栈跟踪(或者可能是记录和重新抛出)。

  2. 仅捕获您知道可以处理的异常,不多不少。如果您不知道方法可能会引发哪些异常,那么您也不会正确处理它们,所以不要捕获。方法和库负责记录您应该能够处理的异常。此外,不要捕获指示逻辑失败的异常,例如 NullReferenceExceptionArgumentException。这些表明您的软件中存在真正的错误,应该修复,而不是在运行时处理。


4
在我看来,逻辑漏洞也应该被发现。最好的方式是失败并记录,而不是让软件崩溃在用户/客户的面前。当然,这些漏洞不应该出现,但它们总是会出现,而客户并不关心什么问题导致了崩溃,他们只会记得你提供了会崩溃的软件。尽管如此,我大部分同意你所说的话。 - user253984
5
客户讨厌功能失常的软件,尤其是他们不知道原因时。当出现未处理异常时,您应该始终关闭应用程序。 - Jeff Yates
1
在这一点上我赞同Jeff Yates,百分之百支持。除了代码使用大量反射的情况可能需要外,我认为任何基于逻辑的异常在运行时被捕获都是一种代码异味。当然,你可以和应该记录它,但要拆除应用程序。 - Greg D
@GregD:我不同意这种观点,即人们不可能知道如何明智地处理未预期的异常。如果类EasyFooDocument有一个构造函数,指定要加载的文件,并且它是从接收到来自“打开文件”对话框的文件名的代码中调用的。在加载过程中可能会出现无数问题,正确处理其中99.44%的方法是通知用户无法加载文件。即使程序员没有预料到特定的异常,很可能也应该以相同的方式处理它。 - supercat
@GregD:如果.NET语言在不捕获异常的情况下让代码知道抛出了哪些异常(例如,允许finally块有一个Exception参数来指示在try块中抛出了什么异常,如果有的话),那么情况可能会好得多。有很多情况下,方法抛出的异常将表明某个数据结构已经损坏,但并不清楚这是否是一个问题。如果数据结构是从无效的用户提供的文件构建的,则丢弃它将解决问题。然而,如果该结构对操作是必需的,... - supercat
显示剩余2条评论

5

是的,那是一种不好的做法。原则是:“捕获你有能力响应的异常,让其他异常消失。”

try {
    File.Open(usersChosenFile, FileMode.Open);
} catch(FileNotFoundException) {
    // tell the user the file is gone, give them a chance to respond
    // this is good
} catch(UnauthorizedAccessException) {
    // this is good too
} catch(Exception) {
    // what did you just catch? Who knows. What if its OutOfMemoryException?
    // Do you really want to deal with that here? Let this one go by
}

4

您运行的方法通常会显示可能抛出的异常。然后您可以相应地捕获。

如果是您自己的代码,通常可以看到将会抛出什么异常,或者使用底层类的异常作为指南,以了解需要捕获哪些异常。

我推荐一些链接:


我认为这个问题是在问如何找出底层类将会抛出什么?例如,Java有一个“throws”子句来记录这一点。而.NET则没有。 - spoulson
@spoul - 把你的鼠标移动到你正在使用的类或方法上,它就会显示出来。或者使用对象资源管理器。 - Kyle Rosendo
4
在我看来,这不是一种可靠的解决方法。我的意思是,当使用 EF 和生成的 POCO 对象时,没有这方面的文档进行参考。使用 Linq 会更加复杂。如果我使用 DI 和 IoC 容器呢?哇,需要考虑很多因素,但几乎没有按照你所建议的方式记录下来的信息! - Jaxidian
“Catch accordingly”是一个相当广泛的陈述……我建议您只捕获和处理那些您知道如何正确处理的异常。例如,我有一个任务需要进行数据库调用。在该任务中,我处理从这些调用中抛出的SQL和网络异常,因为我知道我可以正确地从它们中恢复。在这种情况下,我不会在该上下文中捕获任何其他异常,因为我真的无法从中恢复。(在这种情况下,“正确地从中恢复”意味着我没有破坏应用程序状态或其他资源。) - Curt Nichols

2

更重要的问题是,你是否需要对特定的异常进行特定的错误处理。如果你只需要捕获发生的任何错误,那么使用通用的try/catch块没有任何问题:

try
{
    // Some Code
}
catch
{
}

然而,如果你需要对某些异常做特定处理,你可以在单个try块中指定多个catch块:

try
{
    // Some Code
}
catch(ArgumentException argE)
{
}
catch(NullReferenceException nullE)
{
}
catch(Exception e)
{
    // Everything else
}

如果你无法从异常中恢复,请不要在该级别上捕获它。

1

在我看来 - 除非你打算为其增加价值和/或它只能在该方法中处理,否则不要捕获任何异常。

请确保有一个通用的异常处理程序来处理所有未处理的异常。

希望对你有所帮助。


1
如Kyle所说,请让您的方法长度短小,仅在小范围内放置try/catch。将鼠标悬停在调用的方法上 - 您应该会得到异常列表。 这不会捕获列出的每个异常,但如果在catch(Exception e) {...}中打印异常类型,则也可以通过经验发现异常。您需要的是e.GetType().FullNamee.StackTracee.Messagee.InnerException…或我列出的子集。

0

在使用框架方法时,您可以查看MSDN文档。每个方法描述都有一个可能抛出的异常列表。

例如,请查看File.Open()文档中的异常段落。

在使用自己的方法时,您应该注意您的方法可能抛出的异常。


在Visual Studio中,只需将鼠标悬停在方法上并阅读文档注释,还可以列出抛出的异常类型。 - Matt Greer

0

文档通常描述一个方法可能抛出的异常以及可能发生的条件。这在微软.NET框架的参考文档中尤为突出。

在我看来,只有在有充分理由时才应该捕获异常。这通常意味着以通用方式捕获和处理它们是不必要的。记录异常(异常处理程序中非常常见的活动)应仅在调用堆栈底部或不重新抛出(可能包装的)异常时发生,这应该很少发生。如果您希望在异常向下冒泡时在调用堆栈中的每个帧上执行某些操作,请查看面向方面的编程(AOP)技术。


0

除非在罕见的情况下,我通常认为catch块是代码异味。

通过事先检查条件来防止抛出异常。例如,如果从文件中读取,请使用System.IO中的类检查文件是否存在,而不是使用catch块来处理文件不存在的情况。

catch块不应成为您的应用程序逻辑的一部分。


0

你应该捕获那些你可以开发合理策略来处理问题的异常。如果没有合理的替代方案(稍后再试、使用不同的技术/方法来实现相同的总体目标、告知用户当前无法实现目标以及他们可以采取什么措施来解决问题),那么捕获异常就没有意义。

异常(请原谅):值得在最高级别(例如Application.ThreadException或AppDOmain.UnhandledException)上拥有一些东西,以尝试记录那些你没有处理的异常。如果记录失败,你注定会失败。

但是盲目地吞噬所有异常(特别是在低层次)可能会导致非常令人沮丧的调试/诊断过程。


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