48

我已经阅读了一些关于 C# 异常处理实践的其他问题,但似乎没有人问我在寻找什么。

如果我为特定类或一组类实现我自己的自定义异常。是否应该使用内部异常将与这些类相关的所有错误封装到我的异常中,还是应该让它们失败?

我在想最好捕获所有异常,以便可以立即从我的源代码中识别出异常。我仍然将原始异常作为内部异常传递。另一方面,我认为重新抛出异常是多余的。

例外:

class FooException : Exception
{
    //...
}

选项 1:Foo 封装所有异常:

class Foo
{
    DoSomething(int param)
    {
        try 
        {
             if (/*Something Bad*/)
             {  
                 //violates business logic etc... 
                 throw new FooException("Reason...");
             }
             //... 
             //something that might throw an exception
        }
        catch (FooException ex)
        {
             throw;
        }
        catch (Exception ex)
        {
             throw new FooException("Inner Exception", ex);
        }
    }
}

选项 2: Foo 抛出特定的 FooExceptions 但允许其他异常通过:

class Foo
{
    DoSomething(int param)
    {
        if  (/*Something Bad*/)
        {
             //violates business logic etc... 
             throw new FooException("Reason...");
        }
        //... 
        //something that might throw an exception and not caught
    }
}
4

8 回答 8

55

根据我对库的经验,您应该将所有内容(您可以预期的)包装在 aFooException中,原因如下:

  1. 人们知道它来自您的课程,或者至少来自他们对它们的使用。如果他们看到FileNotFoundException,他们可能会到处寻找。你正在帮助他们缩小范围。(我现在意识到堆栈跟踪服务于这个目的,所以也许你可以忽略这一点。)

  2. 您可以提供更多上下文。用您自己的异常包装 FNF,您可以说“我试图为此目的加载此文件,但找不到它。这暗示了可能的正确解决方案。

  3. 您的库可以正确处理清理。如果你让异常冒泡,你就是在强迫用户清理。如果你正确地封装了你正在做的事情,那么他们不知道如何处理这种情况!

请记住只包装您可以预期的异常,例如FileNotFound. 不要只是包装Exception并希望最好。

于 2011-01-21T16:36:41.023 回答
19

看看这个MSDN-best-practices

如果您想重新抛出捕获的异常,请考虑使用throw而不是,因为这样可以保留原始堆栈跟踪(行号等)。throw ex

于 2011-01-21T16:37:00.623 回答
6

创建自定义异常时,我总是添加几个属性。一种是用户名或 ID。我添加了一个 DisplayMessage 属性来携带要显示给用户的文本。然后,我使用 Message 属性来传达要记录在日志中的技术细节。

我捕获了数据访问层中的每个错误,我仍然可以捕获存储过程的名称和传递的参数的值。或内联 SQL。也许是数据库名称或部分连接字符串(请不要提供凭据)。这些可能会出现在 Message 或他们自己的新自定义 DatabaseInfo 属性中。

对于网页,我使用相同的自定义异常。我将在 Message 属性中放入表单信息——用户在网页上的每个数据输入控件中输入的内容、正在编辑的项目的 ID(客户、产品、员工等)以及用户的操作发生异常时正在服用。

因此,根据您的问题,我的策略是:仅在我可以对异常做些什么时才捕获。很多时候,我所能做的就是记录细节。所以,我只在这些细节可用的地方捕获,然后重新抛出以让异常冒泡到 UI。我在我的自定义异常中保留了原始异常。

于 2011-01-21T16:44:25.513 回答
3

自定义异常的目的是为堆栈跟踪提供详细的上下文信息以帮助调试。选项 1 更好,因为没有它,如果异常发生在堆栈中的“较低”位置,您将无法获得异常的“起源”。

于 2011-01-21T16:34:07.597 回答
1

如果您在 Visual Studio 中运行“异常”的代码片段,您将拥有一个编写异常的良好实践模板。

于 2011-01-21T16:34:16.560 回答
1

注意 选项 1:你throw new FooException("Reason...");不会被抓住,因为它在 try / catch 块之外

  1. 您应该只捕获要处理的异常。
  2. 如果您没有向异常添加任何其他数据而不是使用throw;,因为它不会杀死您的堆栈。在选项 2 中,您仍然可以在 catch 中进行一些处理,然后调用throw;以使用原始堆栈重新抛出原始异常。
于 2011-01-21T16:34:29.380 回答
1

代码在捕获异常时要知道的最重要的事情(不幸的是,Exception 对象完全丢失了该异常)是系统相对于它“应该”的状态(推测异常是因为出现问题而引发的)。如果在 LoadDocument 方法中出现错误,推测文档没有加载成功,但至少有两种可能的系统状态:

  1. 系统状态可能就像从未尝试过加载一样。在这种情况下,如果应用程序可以在没有加载的文档的情况下继续运行,那么它是完全正确的。
  2. 系统状态可能已严重损坏,最好的做法是将可以保存的内容保存到“恢复”文件中(避免用可能损坏的数据替换用户的好文件)并关闭。

显然,在这些极端之间通常还会有其他可能的状态。我建议人们应该努力有一个自定义异常,明确表明状态#1 存在,如果可预见但不可避免的情况可能会导致状态#2,则可能是#2。任何发生并将导致状态 #1 的异常都应包装在指示状态 #1 的异常对象中。如果异常可能以可能危及系统状态的方式发生,则应将它们包装为#2 或允许渗透。

于 2011-03-22T18:21:01.907 回答
0

选项 2 是最好的。我相信最佳实践是仅在您计划对异常执行某些操作时才捕获异常。

在这种情况下,选项 1 只是用您自己的异常包装一个异常。它没有增加任何价值,你的类的用户不能再仅仅捕获 ArgumentException,例如,他们还需要捕获你的 FooException 然后对内部异常进行解析。如果内部异常不是异常,他们能够做一些有用的事情,他们将需要重新抛出。

于 2011-01-21T16:35:25.313 回答