抛出异常:是否封装?

5

我曾经阅读过一篇MSDN文章,鼓励采用以下编程范式(这并不是100%正确的...请参见编辑):

public class MyClass
{
    public void Method1()
    {
        NewCustomException();
    }

    public void Method2()
    {
        NewCustomException();
    }

    void NewCustomException()
    {
        throw new CustomException("Exception message");
    }
}

你认为这种范式有意义吗?将异常消息存储在静态常量字段中,然后将其传递给异常的构造函数,而不是封装整个异常抛出,是否已经足够?
编辑:
使用异常构建器方法。一个类从其实现的不同位置抛出相同的异常是很常见的。为了避免过多的代码,使用帮助方法创建并返回异常。我刚刚注意到(见引用),文章告诉我们要返回异常。
public class MyClass
{
    public void Method1()
    {
        throw NewCustomException();
    }

    public void Method2()
    {
        throw NewCustomException();
    }

    CustomException NewCustomException()
    {
        return new CustomException("Exception message");
    }
}

你对此有何看法?


如果您打算在异常抛出时添加额外的逻辑,并且需要在多个地方添加该逻辑,那么封装异常抛出是非常好的。 - Nick Larsen
啊,好的。但是我真正的意思是只抛出异常,没有任何逻辑。 - Simon
你有这篇文章的链接吗?看一下推荐者的背景可能会很有帮助。 - Jeff Schumacher
@Simon,在你编辑之前已经发布了一个答案。是的,你编辑后的版本是很好的实践。 - Joe
@Joe 我认为你返回什么并不重要。更改异常类型不会破坏任何东西,因为throw将抛出从Exception继承的任何内容。 - Simon
显示剩余2条评论
7个回答

12

我的理解是,传递异常实例是一个不良习惯,即使没有其他原因,你也会失去与异常相关联的堆栈跟踪。调用另一个方法将更改堆栈跟踪,从而使其基本上无用。如果您要采取这种方式,我建议至少从异常中获取堆栈跟踪,并将其作为参数传递给一些帮助器。


他并不是在谈论传递异常实例,而只是将其消息放入一个const中。 - David M

4

在我看来,这是一次过度的重构。您需要返回到堆栈跟踪中的上一行,以查看问题发生的确切位置。如果您的自定义异常总是使用相同的消息,请将其放在CustomException类中。如果它仅在您引用的代码中相同,则将其放在const字段中(您无法使用static const - 它隐式地是static)。


实际上,你也不能将异常放入const字段中。 static readonly是最接近的选择。 - Matti Virkkunen
2
@Matti:他不是在谈论异常,而是它的错误信息。 - David M

3

进行此操作的另一个问题是,有很多地方你甚至无法抛出异常,因为编译器不允许。考虑将这两种方法添加到您的类中:

    public string GetFoo1(bool bar)
    {
        if (bar)
            return "";
        else
            NewCustomException();
    }

    public string GetFoo2(bool bar)
    {
        if (bar)
            return "";
        else
            throw new CustomException("Exception message");
    }

GetFoo1将无法编译,而GetFoo2将会编译通过。


1
关于编译器无法看到封装在方法中的抛出异常这一事实,你提出了一个好观点。 - Joe

2
我会提供一种构建异常的方法,而不是直接抛出异常。如下所示。我记得曾经看到过微软的一条指南建议这样做,但我想不起来在哪里看到的。
使用这种技术,如果出于任何原因要更改异常类型,您只需要在一个地方进行更改(例如从.NET 1.x升级到.NET 2.0时从ConfigurationException更改为ConfigurationErrorsException)。
此外,通过将构建异常的代码及其消息和任何其他数据包含在异常中,您遵守了DRY原则并只有单个代码副本。
显然,在微不足道的情况下,您不会这样做(例如,您不会用BuildArgumentNullException("myParamName")替换throw new ArgumentNullException("myParamName"))。
private static Exception BuildSomeException(... parameters with info to include in the exception ...)
{
    string message = String.Format(...);
    return new SomeException(message, ...);
}

...
throw BuildSomeException(...);

0

我不明白为什么要创建一个只抛出异常的方法。但是,我认为抛出自定义异常是有价值的。如果你抛出的所有异常都是自定义异常的子类,那么你可以快速地看到抛出的异常是你正在处理的异常还是你尚未处理的异常。此外,你可以捕获 MyBaseException,这比捕获 Exception 要好。


0

如果您不知道如何处理异常,那么这样做非常方便。您想要仅仅抛出它吗?或者也许稍后您将在某个地方记录异常然后再抛出它?或者可能传递一些参数(即方法名称等),这些参数与异常捆绑在一起?

在这种情况下,创建一个单独的方法来处理异常情况是很方便的,当您想要更改它时。

我通常不会费心去做这件事 - 相反,只需提前弄清楚如何处理异常(即您将在消息中放置哪些字符串信息)。


0

我通常喜欢将异常消息存储为资源。这有几个用途:

  • 如果需求变成本地化异常消息,这是易如反掌的。
  • 异常消息在开发人员之间更趋于标准化,因为创建一个新的但只稍微不同的消息需要额外的工作。
  • 如果确保消息由标识符引用,并在抛出异常时将标识符包含在异常中,则更容易将消息跟踪到抛出它的代码。

缺点是相比硬编码消息,它需要(仅)略微更多的前期工作。


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