4

我应该在我的方法中捕获异常以“纯粹记录”目的,从而将错误文档封装在方法本身中,还是调用者的责任?

假设我在我的方法中调用了许多其他方法EncryptPackage(),包括框架方法,它们可能会引发许多异常。我将所有内容包装在using块中,因此无需捕获异常进行清理(或者我使用 try/finally 进行清理)。无论如何我应该捕获异常,并提供有关该方法上下文的详细信息,还是调用者方法的责任?

这是案例一:

[Serializable]
class TestClassException : Exception
{
    public TestClassException() : base() { }
    public TestClassException(string message) : base(message) { }
    public TestClassException(string message, Exception innerException) : base(message, innerException) { }
}

class TestClass
{
    public TestClass() { }

    public void EncryptPackage()
    {
        try
        {
            DoSomething();
            DoAnotherThing();
        }
        catch (Exception ex)
        {
            throw new TestClassException("Error occurred during package encryption", ex);
        }
    }
}

class ConsumerExample
{
    public ConsumerExample() { }

    public void DoSomeStuff()
    {
        TestClass testClass = new TestClass();

        try
        {
            testClass.EncryptPackage();
        }
        catch (TestClassException ex)
        {
            System.Windows.Forms.MessageBox.Show(ex.ToString());
        }
    }
}

在此代码中,请注意该EncryptPackage()方法如何捕获所有可能的异常,只是为了“装饰错误文本”,并带有“在包加密期间发生错误”文本。EncryptPackage()这里封装了错误描述逻辑。

这是另一种技术:

class TestClass2
{
    public TestClass2() { }

    public void EncryptPackage()
    {
        DoSomething();
        DoAnotherThing();
    }
}

class ConsumerExample2
{
    public ConsumerExample2() { }

    public void DoSomeStuff()
    {
        TestClass testClass = new TestClass();

        try
        {
            testClass.EncryptPackage();
        }
        catch (Exception ex)
        {
            System.Windows.Forms.MessageBox.Show("Error occurred during package encryption.\r\n\r\n" + ex.ToString());
        }
    }
}

在此示例中,EncryptPackage()不会捕获任何内容,因为调用方无论如何都会使用“包加密期间发生错误。\r\n\r\n”消息记录错误案例。

请注意,这是一个非常简化的示例,在现实世界中会有许多分层类,并且异常会通过长调用堆栈传播,那么捕获异常的方法是首选?第二种方法似乎“更干净”,因为异常是在将要发生一些“实际处理”(例如向用户显示)的层中处理的。调用堆栈信息将保存在异常对象中,因此从技术上讲,可以找出引发异常的确切位置。但是......这似乎不像第一种方法那样“记录良好”,其中每个抽象级别都将自己的描述添加到错误中,将先前的异常保留在innerException成员中。在这种情况下,当执行离开TestClass层,它已经包含在这个类中发生的错误的详细描述。所以这感觉是对错误处理逻辑的更好封装。

使用哪一个?

4

4 回答 4

4

Effective Java 中有一个章节:

较高层应该捕获较低级别的异常,并在它们的位置抛出异常,这些异常可以用较高级别的抽象来解释。这个成语被称为异常翻译。

于 2012-10-01T08:40:26.307 回答
2

我更喜欢您的第二个示例,主要是因为它可以显着减少您必须编写的错误处理代码的数量,特别是如果您正在编写自定义异常 - 对于第一个示例,您最终可能会得到很多不提供的自定义异常类很多好处(你已经有调用堆栈来告诉你异常来自哪里)。

您可能认为有一个更具描述性的错误消息很好,但谁从中受益?最终用户?您是否应该向您的用户显示异常消息(以及您将使用什么语言)?很多时候用户只需要知道发生了内部错误,他们应该放弃(重新启动),或者再试一次。你对开发者有好处吗?无论如何,您可能最终会使用您面前的源代码检查调用堆栈,因此您实际上并不需要更具描述性的消息,您可以自己查看代码此时正在做什么。

这不是一个硬性规定。大多数时候我只在顶层捕获异常,在那里我记录它们并向用户报告错误。如果您直接向用户报告异常,那么原始异常通常不会从翻译中受益,例如,如果您尝试打开一个不存在的文件,那么 System.IO.FileNotFoundException 的描述性就足够了,那么为什么要将它翻译成别的东西?你真的想对所有的异常情况做出同样的判断(“我比图书馆作者更了解,所以我要翻译他们精心设计的例外情况”)?如果我想从异常中恢复(通常是不可能的),我只会捕获较低的异常,或者,我很少想将它们转换为更具描述性的异常。

在分层架构中,在层之间转换异常是有意义的,例如,将来自数据访问层的异常捕获为适合应用层的形式,在应用层和用户界面之间也是如此,但我不这样做不知道您是否正在使用这种类型的系统。

如果您想记录您的异常,您应该使用该方法的 xml 文档中的异常标记。然后可以将其用于文档中的一般帮助文件,例如,使用SandCastle

于 2012-10-01T09:11:13.447 回答
1

根据上面的@Sjoerd,翻译异常,使它们处于相同的抽象级别。在您的情况下,EncryptPackage 应该翻译任何较低级别的异常本身,而不是调用者。

假设较低级别的异常来自 DB 层(例如 DBException)。调用者会期望理解 DBException 吗?答案是否定的:调用者想要加密一个包,而不是一个 DBException。出于调试目的,应将较低级别的异常链接到较高级别的异常内部。

最后,我知道 TestClassException 是一个例子,但要确保异常类清楚地描述了问题:我个人不喜欢平淡的通用异常类(除了为其他异常创建一个通用基类)。

于 2012-10-01T09:58:02.350 回答
0

您应该尝试/捕捉一些容易区分的情况:

  • 可以“从外部”调用的任何方法,例如应用的入口点、UI 事件、多线程调用等。将一些日志输出或消息放在您拥有的每一个捕获中。这将防止您的应用程序崩溃(在大多数情况下),并为您或用户提供有关如何解决问题的一些反馈。

  • 当您真正可以处理异常时。这意味着您的应用可以,例如,选择辅助数据库或服务器 URL,应用不同的处理等。

  • 当您想防止某些可选操作破坏主要工作流程时,例如未能删除临时文件不应导致您的流程失败。

  • 可能还有其他一些地方需要尝试/捕获,但这些应该很少见

始终将错误处理与记录和/或向用户发送消息的体面方式结合起来,不要让任何异常信息消失,因为这是您获取应用程序的方式,并且“没有明显的原因”表现不佳 - 至少原因应该是变得明显。

另外 - 不要使用异常来控制您的工作流程。除非绝对没有其他方法可以做某事,否则真的不应该有任何“投掷”。

于 2012-10-04T16:28:43.347 回答