如果你重新抛出它们包装另一个异常,是否可以捕获所有异常类型?

时间:2010-02-25 20:12:13

标签: c# .net exception-handling

我知道你不应该编写缓存所有异常类型的代码。

try
{
  //code that can throw an exception
}
catch
{
   //what? I don't see no
}

相反,你应该做更像下面代码的事情,允许你没想到的任何其他异常。

try
{
//code that can throw an exception
}
catch(TypeAException)
{
   //TypeA specific code
}

catch(TypeBException)
{
   //TypeB specific code
}

但如果用另一个异常包装它们,是否可以捕获所有异常类型? 考虑下面的Save()方法,我将其作为Catalog类的一部分编写。我是否有任何错误捕获所有异常类型并返回单个自定义CatalogIOException,原始异常作为内部异常?

基本上我不希望任何调用代码必须知道可能在Save()方法中抛出的所有特定异常。他们只需要知道他们是否试图保存只读目录(CatalogReadOnlyException),目录无法序列化(CatalogSerializationException),或者是否存在写入文件的问题(CatalogIOException)。

这是处理异常的好方法还是坏方法?

/// <summary>
/// Saves the catalog
/// </summary>
/// <exception cref="CatalogReadOnlyException"></exception>
/// <exception cref="CatalogIOException"></exception>
/// <exception cref="CatalogSerializingExeption"></exception>
public void Save()
{
    if (!this.ReadOnly)
    {
        try
        {
            System.Xml.Serialization.XmlSerializer serializer = new XmlSerializer(typeof(Catalog));
            this._catfileStream.SetLength(0); //clears the file stream
            serializer.Serialize(this._catfileStream, this);
        }
        catch (InvalidOperationException exp)
        {
            throw new CatalogSerializationException("There was a problem serializing the catalog", exp);
        }
        catch (Exception exp)
        {
            throw new CatalogIOException("There was a problem accessing the catalog file", exp);
        }
    }
    else
    {
        throw new CatalogReadOnlyException();
    }
}

更新1

感谢目前为止的所有回复。这听起来像是共识是我不应该这样做,而且我应该只是抓住异常,如果我真的与它们有关。在这个Save()方法的情况下,确实没有任何可能抛出的异常,我想在Save()方法本身中处理。大多数情况下,我只想通知用户他们无法保存的原因。

我认为我的真正问题是我正在使用异常作为向用户通知问题的方法,而我正在告诉我如何创建和处理异常有点过多。因此,听起来似乎最好不要捕获任何异常并让UI层弄清楚如何通知用户,或者崩溃。它是否正确?考虑下面的Save Menu事件处理程序。

    private void saveCatalogToolStripMenuItem_Click(object sender, EventArgs e)
    {
        //Check if the catalog is read only
        if (this.Catalog.ReadOnly)
        {
            MessageBox.Show("The currently opened catalog is readonly and can not be saved");
            return;
        }

        //attempts to save
        try
        {
            //Save method doesn't catch anything it can't deal with directly
            this.Catalog.Save(); 
        }
        catch (System.IO.FileNotFoundException)
        {
            MessageBox.Show("The catalog file could not be found");
        }
        catch (InvalidOperationException exp)
        {
            MessageBox.Show("There was a problem serializing the catalog for saving: " + exp.Message);
        }
        catch (System.IO.IOException exp)
        {
            MessageBox.Show("There was a problem accessing the catalog file: " + exp.Message);
        }
        catch (Exception exp)
        {
            MessageBox.Show("There was a problem saving the catalog:" + exp.Message);
        }
    }

更新2

还有一件事。如果Save()方法是公共API与内部代码的一部分,答案是否会发生变化?例如,如果它是公共API的一部分,那么我必须弄清楚并记录Save()可能抛出的所有可能的异常。如果知道Save()只能抛出我的三个自定义异常中的一个,那就容易多了。

此外,如果Save()是公共API的一部分,那么安全性也不是一个问题吗?也许我想让API的消费者知道保存不成功,但我不希望通过让他们得到可能已经生成的异常来揭露有关Save()如何工作的任何内容。

12 个答案:

答案 0 :(得分:7)

做一个通用的catch-all和rethrowing作为一种新的异常并不能真正解决你的问题而且不会给你任何东西。

你真正需要做的是捕捉你可以处理的异常然后处理它们(在适当的级别 - 这是重新抛出可能有用的地方)。所有其他异常都需要记录,以便您可以调试它们发生的原因,或者不应该首先发生(例如 - 确保验证用户输入等)。如果你发现所有异常,你将永远不会知道为什么你得到了你所得到的例外,因此无法修复它们。

更新回复

为了回应您的问题更新(特别是您希望如何处理保存案例),我的问题是 - 为什么您使用例外作为确定程序路径的方法?例如,让我们采取“FileNotFoundException”。显然有时会发生这种情况。但是,在保存(或做任何事情)文件之前,不要让问题发生并通知用户,为什么不首先检查文件是否可以找到。您仍然可以获得相同的效果,但您没有使用异常来控制程序流。

我希望这一切都有道理。如果您有任何其他问题,请与我们联系。

答案 1 :(得分:5)

当您使用原始异常作为内部异常重新抛出时,您将丢失原始堆栈跟踪,这是有价值的调试信息。

我有时会按照你的建议行事,但我总是先记录原始异常以保留堆栈跟踪。

答案 2 :(得分:3)

我没有看到你正在做什么的问题。在自定义异常类型中包装异常的原因是在代码层之间创建抽象 - 将较低级别的错误转换为更高级别的上下文。这样做可以减轻调用代码对Save所做的实现细节的过多了解。

您的update #1是调用代码必须过分了解Save()的实现细节的示例。为了回应您的第二次更新,我同意100%

<强> PS
我并不是说在遇到异常的每个场景中都这样做。只有当收益超过成本时(通常在模块边界)。

当这个特别有用时的示例场景:您正在包装第三方库,您还不知道可能引发的所有潜在异常,您没有源代码或任何文档,等等。

此外,他正在包装基础异常并且没有信息丢失。仍然可以正确记录异常(尽管您需要通过InnerException s)进行递归。

答案 3 :(得分:2)

我赞成包装异常,从自定义异常层次结构可以将异常划分为比默认层次结构更有用的分类的观点来看。假设有人试图打开文档并获取ArgumentException或InvalidOperationException。异常的类型是否确实包含任何有用的信息?但是,假设有一个CodecNotFoundException,PrerenderFingFailureException或FontLoadFailureException。可以想象系统捕获一些异常并尝试对其做一些事情(例如,允许用户搜索CODEC,使用较低分辨率设置重试渲染,或在警告用户后允许替换字体)。比默认的例外更有用,其中很多都没有说明什么是真正的错误或者可以做些什么。

从层次结构的角度来看,真正需要的是一种区分异常的方法,这些异常表明抛出异常的方法无法执行其任务,但系统状态类似于方法启动之前的状态,以及这表明系统状态的破坏方式超出了方法失败所隐含的方式。正常的异常层次对此完全没用;如果一个包装异常,则可以稍微改善一下情况(尽管不如层次结构设计得更好)。强制事务被解除的异常并不像提交或解除事务时那样坏。在前一种情况下,系统的状态是已知的;在后一种情况下,它不是。

虽然应该避免捕获某些非常糟糕的异常(StackOverflowException,OutOfMemoryException,ThreadAbortException),但我不确定它是否真的重要。如果系统崩溃并烧毁,无论是否捕获异常,它都会这样做。在vb.net中,可能值得“在IsNotHorribleException(Ex)时捕获Ex Exception”,但C#没有这样的构造,甚至也没有办法排除某些异常被捕获。

分手注意:在某些情况下,一个操作可能会生成多个值得记录的异常。只有通过在包含其他异常列表的自定义异常中包装异常才能真正实现。

答案 4 :(得分:1)

我认为这不是一个好主意。

如果您要添加任何内容,则只应添加自己的异常类型。

此外,您应该只捕获您期望的异常,并且您能够处理 - 所有其他异常应该被允许冒泡。

作为一名开发人员,我必须说,如果你试图通过吞咽或包裹它来“隐藏”我的例外情况,我会生气。

答案 5 :(得分:1)

有关捕获(异常)为何错误的更多信息,请查看本文:http://blogs.msdn.com/clrteam/archive/2009/02/19/why-catch-exception-empty-catch-is-bad.aspx

基本上捕捉'例外'就像是说'如果出现问题我不关心继续'并捕捉'异常'并将其包裹起来就像是说“如果出现任何问题,请将它们视为完全出错原因”。

这不是正确的你要处理它,因为你半预期它或你完全不认为它应该永远发生(或不知道它会)。在这种情况下,您需要某种应用级别的日志记录,以指出您从未预料到的问题 - 而不仅仅是一个适合所有解决方案的问题。

答案 6 :(得分:1)

我自己的经验法则是只有在我可以添加一些有用的上下文时才能捕获并包装Exception,例如我尝试访问的文件名,或连接字符串等。如果{{{1} 1}}在您的用户界面中弹出,没有附加任何其他信息,您将有一个时间来追踪错误。

只有当我想要添加的上下文或消息对于该异常更有用时,我才会捕获特定的异常类型,而不是我对InvalidOperationException所说的那样。

否则,我将异常冒泡到另一个可能有一些有用的方法添加。我不想做的是将自己绑在试图捕获并声明和处理每种可能的异常类型的结上,特别是因为你永远不知道运行时何时会抛出偷偷摸摸的ExceptionThreadAbortException。 / p>

所以,在你的例子中,我会做这样的事情:

OutOfMemoryException

考虑将内部异常的消息添加到您的包装器异常中,这样如果用户只是向您发送错误对话框的屏幕截图,您至少会拥有所有消息,而不仅仅是顶部消息;如果可以的话,将整个try { System.Xml.Serialization.XmlSerializer serializer = new XmlSerializer(typeof(Catalog)); this._catfileStream.SetLength(0); //clears the file stream serializer.Serialize(this._catfileStream, this); } // catch (InvalidOperationException exp) // Don't catch this because I have nothing specific to add that // I wouldn't also say for all exceptions. catch (Exception exp) { throw new CatalogIOException( string.Format("There was a problem accessing catalog file '{0}'. ({1})", _catfileStream.Name, exp.Message), exp); } 写入某个日志文件。

答案 7 :(得分:0)

在这种特殊情况下,异常应该是非常罕见的,包装它不应该是有用的东西,并且可能会妨碍错误处理。 .Net框架中有很多例子,我可以处理的特定异常包含在一个更普遍的异常中,这使我处理特定情况变得更加困难(尽管不是不可能)。

答案 8 :(得分:0)

我之前写了一篇关于这个话题的文章。在其中,我重申尽可能多地捕获有关异常的数据的重要性。这是文章的URL:

http://it.toolbox.com/blogs/paytonbyrd/improve-exception-handling-with-reflection-and-generics-8718

答案 9 :(得分:0)

用户从被告知“序列化目录时出现问题”有什么好处?我想你的问题域可能是一个特例,但是我编写过的每一组用户在阅读该消息时都会以同样的方式响应:“程序爆炸了。有关目录的东西。”

我不是故意屈服于我的用户;我不是。一般来说,我的用户与他们的关注有更好的关系,而不是浪费它构建一个细粒度的心理模型来解释我的软件内部正在发生的事情。曾经有一段时间我的用户必须建立这样的理解才能使用我编写的程序,而且我可以告诉你,这些经历对他们或对我来说都不是有益的。

我认为你花时间更好地花时间来确定如何以一种形式可靠地记录异常和相关的应用程序状态,当你的用户告诉你某些事情发生时你可以访问它而不是提出一个复杂的结构来产生错误人们不太可能理解的信息。

答案 10 :(得分:0)

要回答这个问题,你需要了解为什么捕捉System.Exception是一个坏主意,并了解你自己的动机来做你的建议。

这是一个坏主意,因为它使得任何可能出错的声明没有问题,并且应用程序处于良好状态以便继续运行。这是一个非常大胆的陈述。

所以问题是:你提出的建议是否等同于捕获System.Exception?是否为您的API消费者提供了更多的知识以便做出更好的判断?它是否只是鼓励他们捕获YourWrappedException,这是捕获System.Exception的道德等价物,除了不触发FXCop警告?

在大多数情况下,如果您知道有一条规则禁止做某事,并想知道类似的东西是否“好”,那么您应该首先了解原始规则的基本原理。

答案 11 :(得分:-1)

这是.NET中的标准做法,以及如何处理异常,尤其是对于可从中恢复的异常。

编辑:我真的不知道为什么我被贬低,也许我比其他人更多地了解作者的意图。但是我读取代码的方式是将这些异常包装到他的自定义异常中,这意味着该方法的使用者可以处理这些异常,并且消费者有责任处理错误处理。

DV并没有一个人真正留下任何形式的实际争议。我坚持认为这是完全可以接受的,因为消费者应该意识到这可能引发的潜在异常以及处理它们的能力,这可以通过显式包装异常来显示。这也保留了原始异常,因此可以使用堆栈跟踪并可以访问基础异常。