繁体   English   中英

C#:抛出自定义异常最佳实践

[英]C#: Throwing Custom Exception Best Practices

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

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

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

例外:

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
    }
}

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

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

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

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

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

看看这个MSDN最佳实践

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

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

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

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

所以,根据你的问题,我的策略是:只有在我可以对异常做些什么时才会抓住。 通常,我所能做的就是记录细节。 所以,我只抓住那些细节可用的点,然后重新抛出让异常泡到UI。 我在自定义异常中保留了原始异常。

自定义异常的目的是为堆栈跟踪提供详细的上下文信息以帮助调试。 选项1更好,因为没有它,如果它在堆栈中出现“较低”,则不会获得异常的“起源”。

如果在Visual Studio中运行“异常”的代码片段,则会有一个良好练习异常编写的模板。

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

  1. 您应该只捕获要处理的异常。
  2. 如果你没有向异常添加任何额外数据而不是使用throw; 因为它不会杀死你的堆栈。 在选项2中,你仍然可以在catch中进行一些处理并且只是调用throw; 用原始堆栈重新抛出原始异常。

对于代码来说,最重要的是要知道何时捕获异常(不幸的是,异常对象完全丢失)是系统相对于“应该”的状态(可能是由于出现了错误而抛出了异常)。 如果LoadDocument方法中发生错误,可能是文档未成功加载,但至少有两种可能的系统状态:

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

显然,在这些极端之间经常会有其他可能的状态。 我建议人们应该努力拥有一个自定义异常,明确指出状态#1存在,如果可预见但可能会导致#2,可能会导致#2。 发生并将导致状态#1的任何异常都应该包含在指示状态#1的异常对象中。 如果异常可能以系统状态可能受到损害的方式发生,则它们应该被包装为#2或允许渗透。

选项2是最好的。 我认为最佳做法是仅在您计划对异常执行某些操作时捕获异常。

在这种情况下,选项1只是用您自己的异常包装异常。 它没有添加任何值,并且您的类的用户不能再只捕获ArgumentException,例如,他们还需要捕获您的FooException然后解析内部异常。 如果内部异常不是例外,他们可以做一些有用的事情,他们需要重新抛出。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM