如何在c#/.net中记录抛出的异常

159

我目前正在编写一个小框架,将由公司内其他开发人员使用。

我想提供良好的Intellisense信息,但我不确定如何记录抛出的异常。

在下面的例子中:

public void MyMethod1()
{
    MyMethod2();

    // also may throw InvalidOperationException
}

public void MyMethod2()
{
    System.IO.File.Open(somepath...); // this may throw FileNotFoundException

    // also may throw DivideByZeroException
}

我知道用于文档化异常的标记语言是:

/// <exception cref="SomeException">when things go wrong.</exception>

我不明白的是如何记录由 MyMethod1() 调用的代码抛出的异常?

  • 我应该记录由 MyMethod2() 抛出的异常吗?
  • 我应该记录由 File.Open() 抛出的异常吗?

记录可能出现的异常的最佳方式是什么?


5
我知道这不完全是你所问的(而且这是一个非常古老的问题),但是微软C#编译器和设计团队的主要开发人员Eric Lippert写了一篇关于4种异常类型的博客文章,我认为每个开发人员在编写异常处理代码时都应该考虑到:http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx - javajavajavajavajava
@javajavajavajavajava 感谢分享链接 - 绝对值得一读。 - Arnold Zokas
1
我认为这是一个有效的问题,因为在C#中如何正确记录异常并不明显,而50K的浏览量表明很多人也不清楚。第二个得票最高的答案非常有帮助,因为它展示了如何使用现有的xmldocs来记录此信息。投票重新开放。这个“基于观点”的关闭原因正在扼杀许多实际上非常有用的编程问题。 - Alexei - check Codidact
2
看起来上面评论中的链接已经失效了。这里提供一个存档链接:https://web.archive.org/web/20220616181318/https://ericlippert.com/2008/09/10/vexing-exceptions/ - Jakob Lovern
8个回答

124

您应该记录代码可能引发的每个异常,包括您可能调用的任何方法中的异常。

如果列表变得有点大,您可能需要创建自己的异常类型。在您的方法中捕获可能遇到的所有异常,将它们包装在您的异常中并抛出。

另一个您可能希望以这种方式执行的地方是,如果您的方法位于API的前面。就像门面将多个接口简化为单个接口一样,您的API应该将多个异常简化为单个异常。这使得调用者更容易使用您的代码。


为了回答Andrew的一些疑虑(来自评论),有三种类型的异常:你不知道的异常,你知道但无法处理的异常,以及你知道且可以处理的异常。
你想要放弃那些你不知道的异常。这是快速失败的原则——最好让你的应用程序崩溃,而不是进入可能导致数据损坏的状态。崩溃会告诉你发生了什么以及为什么会发生,这可能有助于将该异常移出“你不知道的异常”列表。
你知道但无法处理的异常是像OutOfMemoryExceptions这样的异常。在极端情况下,您可能希望处理此类异常,但除非您有一些非常显著的要求,否则您将像第一类一样对待它们——让它们消失。您必须记录这些异常吗?在每个新建对象的方法上记录OOMs会显得非常愚蠢。
你知道且可以处理的异常是你应该记录和包装的异常。
您可以在这里找到更多关于异常处理的指南。

3
我必须承认这听起来不太实用。我无法想象我所调用的任何代码可能会抛出多少潜在的异常,而且还有像OutOfMemoryException这样的东西,你不希望捕获并包装它们。 - Andrew Hare
3
你的回答很好,但实际上有两个答案互相矛盾。一方面是“记录代码可能抛出的每一个异常”,另一方面是“只需记录你已知并能够处理的异常”。请问你认为哪个更好? - tymtam
2
@Tymek:不对。前半部分回答了“我应该如何记录异常”的问题,而后半部分则指出了“我应该记录哪些异常”的显而易见的答案。前半部分并不意味着您需要记录可能发生的每个异常。有些人太过字面,这就需要后半部分的解释。 - user1228
5
@Tymek,我认为你的观点可能是,如果你能做些什么来解决问题,为什么不去解决它,而是重新抛出并记录它?更准确的说法可能是“你知道客户端代码可以处理的异常”,这样就避免了矛盾,因为这些是需要记录的理想异常情况。 - mo.
我们这里不谈论那种低俗的语言。 - user1228
显示剩余2条评论

121

你应该使用标准的XML文档

/// <exception cref="InvalidOperationException">Why it's thrown.</exception>
/// <exception cref="FileNotFoundException">Why it's thrown.</exception>
/// <exception cref="DivideByZeroException">Why it's thrown.</exception>
public void MyMethod1()
{
    MyMethod2();
    // ... other stuff here
}

/// <exception cref="FileNotFoundException">Why it's thrown.</exception>
/// <exception cref="DivideByZeroException">Why it's thrown.</exception>
public void MyMethod2()
{
    System.IO.File.Open(somepath...);
}

/// <exception cref="FileNotFoundException">Why it's thrown.</exception>
public void MyMethod3()
{
    try
    {
        MyMethod2();
    }
    catch (DivideByZeroException ex)
    {
        Trace.Warning("We tried to divide by zero, but we can continue.");
    }
}

这样做的价值在于,您提供了可以发生的已知异常的文档。如果您使用 Visual Studio,则该文档可在智能感知中使用,以便稍后提醒您(或其他人)可以预期的异常。

您希望指定特定的异常类型,因为您可能能够处理某一类型的异常,而其他类型的异常是严重问题的结果,无法纠正。


1
那有什么价值呢?例如,所有这些异常都是Exception类型的派生。从我的经验来看,想到在方法中调用的其他API可能抛出的每种异常类型是不切实际的。我的观点是,我们不应该担心从方法中抛出的任何异常,而是那些携带任何业务信息的异常。 - Illuminati
1
链接已损坏。 - chtenb

36
您可以使用多个优秀的插件来简化文档编写过程。其中之一是Visual Studio的免费插件GhostDoc,它会生成XML-doc注释。此外,如果您使用ReSharper,请查看ReSharper的优秀插件Agent Johnson Plugin,它添加了一个选项,用于为抛出的异常生成XML注释。 更新: 看起来Agen Johnson并不适用于R# 8,请尝试作为替代方案使用Exceptional for ReSharper...

步骤1:GhostDoc生成XML 注释(Ctrl-Shift-D),而ReSharper的Agent Johnson插件 建议也对异常进行记录:

step 1

步骤2:使用ReSharper的快捷键 (Alt-Enter)添加异常 文档:

步骤2 http://i41.tinypic.com/osdhm

希望这能帮到您 :)

1
tinypic的链接已经失效。 - ANeves

12

据我所知,使用 <exception> 元素的意图是在装饰方法时使用,而不是异常本身:

/// <summary>Does something!</summary>
/// <exception cref="DidNothingException">Thrown if nothing is actually done.</exception>
public void DoSomething()
{
// There be logic here
}

调用的其他方法可能引发的异常应该在这些方法中捕获、处理和记录文档。可能由.NET引发的异常或由您自己的代码明确引发的异常应该被记录。

如果需要更加具体,您可以捕获并抛出自定义异常吗?


4

你的方法的一部分合约应该是检查前置条件是否有效,因此:

public void MyMethod2()
{
    System.IO.File.Open(somepath...); // this may throw FileNotFoundException
}

变成

/// <exception cref="FileNotFoundException">Thrown when somepath isn't a real file.</exception>
public void MyMethod2()
{
    FileInfo fi = new FileInfo( somepath );
    if( !fi.Exists )
    {
        throw new FileNotFoundException("somepath doesn't exists")
    }
    // Maybe go on to check you have permissions to read from it.

    System.IO.File.Open(somepath...); // this may still throw FileNotFoundException though
}

采用这种方法,你可以更轻松地记录所有显式抛出的异常,而无需记录可能会抛出OutOfMemoryException等异常。


1
如果您只是要复制Open调用会抛出的异常,那么检查的意义何在呢?更不用说,正如您所指出的,这里存在竞争条件,检查并不能保证Open操作一定成功... - Matt Enright
1
@MattEnright 承认,但我有点刻意地制造了这个情境来阐明观点... - Rowland Shaw

1
你应该记录方法可能抛出的所有异常。
为了隐藏实现细节,我会尝试自己处理MyMethod2中的一些异常。
如果无法处理或解决异常,可以考虑重新抛出它们。通常会将其封装在更有意义的异常中返回给调用者。

1

确实,正如已经回答的那样,记录异常的方法是使用XML注释。

除了插件之外,您还可以使用静态分析工具,这些工具可以与TFS集成,以确保您已记录异常。

在下面的链接中,您可以看到如何构建一个自定义规则,用于验证StyleCop抛出的异常是否已被记录。

http://www.josefcobonnin.com/post/2009/01/11/Xml-Documentation-Comments-Exceptions-I.aspx http://www.josefcobonnin.com/post/2009/01/15/Xml-Documentation-Comments-Exceptions-II.aspx

敬礼。


0
在你的方法中记录预期的异常,在你的例子中,我会让用户知道该方法可能会抛出文件未找到异常。
记住,这是为了通知调用者要预料什么,以便他们可以选择如何处理它。

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