空的catch块

41

有时候我会遇到需要捕获异常但从不处理的情况。换句话说,异常可能会发生,但它并不重要。

我最近读了一篇关于类似事情的文章:http://c2.com/cgi/wiki?EmptyCatchClause

这个人谈到了如何使用空catch子句的注释:

// should never occur 

这是一种代码异味,不应该出现在代码中。然后他们继续解释说,注释

// don't care if it happens

这完全不同,我自己也遇到过这样的情况。例如,在发送电子邮件时,我会执行类似于以下操作:

var addressCollection = new MailAddressCollection();
foreach (string address in addresses)
{
    try
    {
        addressCollection.Add(address);
    }
    catch (Exception)
    {
        // Do nothing - if an invalid email occurs continue and try to add the rest
    }
}

现在,你可能认为这样做是个坏主意,因为你想要返回给用户并解释一条或多条消息无法发送到收件人。但是如果只是一个抄送地址呢?那就不那么重要了,即使其中一个地址无效(可能只是一个拼写错误),你仍然希望发送该消息。

所以,我使用空的catch块是正确的吗?还是有更好的选择,我不知道?


7
你怎么知道Exception与CC地址有关,而不是例如内存不足的情况?你打算如何从中恢复? - Zdeslav Vojkovic
7
在您的示例中,即使只是记录下来,我仍会处理异常情况--可能甚至不会让用户选择如何处理失败的电子邮件地址,但将其记录下来以便您知道已发生此情况也是好的。 - Arran
1
我怀疑作者的意图是,一条注释说“这个错误永远不应该发生”意味着编写者没有考虑如果出错超出预期应该如何处理。第二个注释是“更好的”,但更好的注释将解释为什么你不关心这个错误。最终,最好的异常处理取决于你的应用程序应该完成什么任务。在某些情况下,吞噬异常而没有进一步的操作是可以接受的。在其他情况下,您可能需要先记录下来,而在某些情况下,您实际上需要尝试恢复。 - ThatBlairGuy
1
感谢您的评论。我同意您的观点,应该捕获与地址格式相关的特定异常,并让其他异常上升。我会进行这个更改。 - Serberuss
4
即使我们假设不在乎因为电子邮件无效,记录异常并注意无效的电子邮件是有好处的。空异常只是“懒惰”编程的借口。 - Chibueze Opata
@ChibuezeOpata 我认为你的意思是“例子”而不是“借口”。 - Nick Freeman
5个回答

103

如果您确实希望在某种类型的异常发生时什么都不做,完全可以使用空的catch块。您可以通过仅捕获您期望发生且知道可以安全忽略的异常类型来改进您的示例。通过捕获“Exception”,您可能会隐藏错误并使自己难以调试程序。

关于异常处理需要记住一件事:用于表示外部错误的异常和用于指示编程错误的异常之间存在很大的区别,前者在至少有时会发生,而后者则不是。第一种情况的一个例子是表示电子邮件无法发送因为连接超时或文件无法保存因为磁盘空间不足的异常。第二种情况的一个例子是指示您尝试将错误类型的参数传递给方法或尝试访问超出边界的数组元素的异常。

对于第二种情况(编程错误),仅“吞噬”异常是个大错误。通常最好的做法是记录堆栈跟踪,然后弹出错误消息告诉用户内部错误已经发生,请他们发送日志给开发人员(即您)。或者在开发过程中,您可以让它将堆栈跟踪打印到控制台并使程序崩溃。

对于第一种情况(外部问题),没有什么规则可以确定“正确”的做法。这完全取决于应用程序的细节。如果您想忽略某些条件并继续,则可以这样做。

总之:

很好,你正在阅读技术书籍和文章,这是非常有收获的。但请记住,在阅读过程中,你会发现许多人给出建议,称某种做法 总是 错误或者 总是 正确。这些观点常常界限分明,近乎于宗教信仰。不要因为一本书、一篇文章(或者在 SO 上的答案... <咳咳>)告诉你某一种方法绝对“正确”,就盲目相信。每个规则都存在着例外情况,那些写这些文章的人不知道你应用程序的详情,而你自己清楚。确保你所阅读的内容有意义,如果没有,请相信自己。


很棒的文章,感谢提供信息。我同意你的最后一点,我更倾向于从正式出版的来源获取信息,而不是从博客或互联网文章中获得。 - Serberuss
24
为绝对陈述做出一个关于绝对陈述的绝对陈述,可笑。 - Nick Freeman
1
我不同意你的观点。是的,你说得对,忽略异常处理确实会使调试更容易,但这只是懒惰编程的表现,也显示了程序员的技能水平。我知道有些人会争论一个空格应该放在这里还是那里,但捕获所有异常才是好的编程习惯。虽然很少见,但即使是你所举的第一个异常例子,也应该被捕获,即使只是为了记录日志。如果我调用的函数无法写入磁盘或发送电子邮件,我希望知道,这将帮助我确定下一步行动以确保数据完整性。 - Guy Park
@NickFreeman,你是在说这样做总是错的吗? ;) - Henrik Erlandsson

23

在合适的地方,一个空的 catch 块是可以的 - 虽然从你贴出来的样例来看,我会肯定地说你不应该使用 catch (Exception)。你应该捕获你预期发生的明确异常。

原因是,如果你把所有异常都吞掉了,那么你将吞掉一些你根本没想到的关键缺陷。"我无法发送到这个电子邮件地址"和"你的计算机磁盘已满"之间有很大的区别。如果你的磁盘已满,你不希望继续尝试发送下一批10000封邮件!

“不应该发生”和"无所谓它是否发生"之间的区别在于,如果"不应该发生",那么当它确实发生时,你不希望悄悄地吞掉它!如果这是一个你从未预料过的条件,通常你希望你的应用程序崩溃(或至少干净地终止并记录详细信息)以便你可以识别这种不可能的情况。


3
对于最后一段落给予加分。那些"不应该发生"的错误往往有着违反预期的神奇习惯,尤其是在应用存在一段时间并可能经历了一些修订之后。 - ThatBlairGuy
在“不应该发生”的问题上达成一致。如果它们不应该发生,你可能会意识到你确实关心它们。 - Robert Kerr

7
如果一个异常不应该被抛出,那么捕获它就没有意义 - 它永远不应该发生,如果发生了,您需要知道。如果有特定的场景可能导致失败,而您可以接受这种情况,则应捕获并测试这些特定的场景,并在所有其他情况下重新抛出,例如:
foreach (string address in addresses)
{
    try
    {
        addressCollection.Add(address);
    }
    catch (EmailNotSentException ex)
    {
        if (IsCausedByMissingCcAddress(ex))
        {
            // Handle this case here e.g. display a warning or just nothing
        }
        else
        {
            throw;
        }
    }
}

请注意,上述代码捕获了特定的(虚构的)异常,而不是捕获Exception。我可以想到很少的情况下可以合法地捕获Exception,相比之下,捕获你预期要抛出的某些特定的异常类型更为合适。

我明白你的意思。我猜你的EmailNotSendException是指与无效电子邮件地址相关的异常被归为一组的想法? - Serberuss
@Serberuss 是的-捕获你能捕获到的最具体的异常。 - Justin

7
许多其他答案给出了合适捕获异常的好理由,然而许多类支持不抛出异常的方法。通常这些方法会在前面加上前缀“Try”。函数不会抛出异常,而是返回一个布尔值,表示任务是否成功。一个很好的例子是ParseTryParse
string s = "Potato";
int i;
if(int.TryParse(s, out i))
{
    //This code is only executed if "s" was parsed succesfully.
    aCollectionOfInts.Add(i);
}

如果您在循环中尝试上述功能并将其与其Parse + Catch等效项进行比较,则TryParse方法将更快。

同意,我非常喜欢使用 Try 方法。 - Serberuss
是的,错误的用户输入并不是一个异常情况,而是一种预期情况。人类不像计算机那样可预测。在这种情况下抛出异常似乎更像是一种令人烦恼的异常而不是其他任何东西。 - Nick Freeman

2
使用一个空的catch块只是吞噬了异常,即使它向你报告了一个Exception的发生,我仍然会处理它。同时,捕获通用的Exception是一种不好的实践,因为它可能会隐藏应用程序中的错误。例如,你可能已经捕获了一个你没有意识到正在发生的ArgumentOutOfRange异常,然后将其吞噬掉(即没有做任何处理)。

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