为什么空捕获会阻止一个坏主意?[关闭]


192

我刚刚在try-catch上看到了一个问题,哪些人(包括Jon Skeet)说空的捕获块是一个真正的坏主意?为什么这个?难道没有一个空的陷阱不是错误的设计决定吗?

我的意思是,例如,有时您想从某个地方(Web服务,数据库)获得一些其他信息,而您实际上并不在乎是否会获得此信息。因此,您尝试获取它,并且如果发生任何事情,那没关系,我只需添加一个“ catch(忽略了异常){}”,仅此而已


53
我会写一条评论解释为什么它应该为空。
Mehrdad Afshari 2009年

5
...或至少记录它已被捕获。
马特b

2
@ocdecio:避免将其用作清理代码,而不是通常避免使用它。
约翰·桑德斯

1
@ocdecio:避免使用try..catch的另一种情况是,当捕获已知类型的故障的异常时。例如,数值转换例外
MPritchard

1
@ocdecio-try..finally优于try..empty catch,因为错误继续在堆栈中出现-由框架处理或杀死程序并显示给用户-两种结果都比“静默失败”。
Nate

Answers:


297

通常,空的try-catch是一个坏主意,因为您无声地吞下了错误条件,然后继续执行。有时这可能是正确的做法,但是通常这表明开发人员看到了异常,不知道该怎么办,因此使用空捕获来使问题静音。

这在编程上等同于将黑色胶带盖在发动机警告灯上。

我相信您如何处理异常取决于您正在使用的软件的哪一层:Rainforest中的异常


4
在那些我真的不需要例外并且由于某些原因无法进行日志记录的真正罕见的情况下,我要确保评论它是有意的-以及为什么不需要它,并重申我仍然愿意在以下情况下进行日志记录:在那种环境下是可行的。基本上,如果在这种情况下可以选择,则我的评论要比日志记录更有用。幸运的是,我只有2个客户,这是一个问题,仅当从他们的网站发送非关键电子邮件时才如此。
约翰·鲁迪

37
也喜欢类比-和所有规则一样,也有例外。例如,如果您决不打算进行定时录音(对于所有记住VCR是什么的老人们),最好在录像机的闪烁时钟上使用黑色胶带。
迈克尔·伯

3
就像任何偏离公认惯例的做法一样,请在适当的情况下进行操作并记录下来,以便下一个人知道您的操作及其原因。
David Thornley,2009年

5
@Jason:至少,您应该包括一条详细的注释,解释为什么要使异常沉默。
内德·巴切尔德

1
这个答案有两点假设:1.引发的异常确实是有意义的,并且编写引发代码的人知道他在做什么。2.异常确实是“错误情况”,不会被滥用为控制流程(尽管这是我的第一点更具体的情况)。
Boris B.

37

通常,这是一个坏主意因为这是真正罕见的情况,其中故障(异常情况,更笼统地说)可以适当地满足而没有任何响应。最重要的是,空catch块是使用异常引擎进行错误检查的人们常用的工具,他们应该先行进行错误检查。

永远都是不好的,这是不正确的……几乎没有错。在某些情况下,您可能不在乎是否有错误,或者错误的存在以某种方式表明您无论如何都无法对其进行任何处理(例如,在将先前的错误写入文本日志文件时,您会收到一个IOException,表示您无论如何都无法写出新错误)。


11

我不会说什么,谁使用空的catch块是一个糟糕的程序员,也不知道他在做什么...

如有必要,我使用空的捕获块。有时,我正在使用的库程序员不知道他在做什么,甚至在没人需要它的情况下也会引发异常。

例如,考虑一些http服务器库,我不会在乎服务器是否抛出异常,因为客户端已断开连接并且index.html无法发送。


6
您肯定过于概括了。仅仅因为不需要它,并不意味着没有人需要它。即使绝对不能采取任何措施来应对,但仍有某人需要收集有关废弃连接的统计信息。因此,假设“库程序员不知道他在做什么”是很不礼貌的。
Ben Voigt

8

在极少数情况下可以证明其合理性。在Python中,您经常会看到这种构造:

try:
    result = foo()
except ValueError:
    result = None

因此,可以执行以下操作(取决于您的应用程序):

result = bar()
if result == None:
    try:
        result = foo()
    except ValueError:
        pass # Python pass is equivalent to { } in curly-brace languages
 # Now result == None if bar() returned None *and* foo() failed

在最近的.NET项目中,我不得不编写代码来枚举插件DLL,以找到实现特定接口的类。相关的代码位(在VB.NET中,抱歉)是:

    For Each dllFile As String In dllFiles
        Try
            ' Try to load the DLL as a .NET Assembly
            Dim dll As Assembly = Assembly.LoadFile(dllFile)
            ' Loop through the classes in the DLL
            For Each cls As Type In dll.GetExportedTypes()
                ' Does this class implement the interface?
                If interfaceType.IsAssignableFrom(cls) Then

                    ' ... more code here ...

                End If
            Next
        Catch ex As Exception
            ' Unable to load the Assembly or enumerate types -- just ignore
        End Try
    Next

尽管即使在这种情况下,我也承认将故障记录在某个地方可能会有所改善。


在看到您的答案之前,我曾将log4net称为这些实例之一。
Scott Lawrence

7

通常会插入空的catch块,因为编码人员实际上并不知道他们在做什么。在我的组织中,一个空的catch块必须包含一个注释,说明为什么不进行任何例外处理是一个好主意。

与此相关的是,大多数人都不知道try {}块后面是否可以接上catch {}或finally {},只需要一个。


1
+1我要在最后一句话中强调“或”以增加影响
MPritchard

但是,第二段可能取决于语言,对吗?
David Z

3
当然,除非你是在谈论IE6还是IE7 ;-) ...这里渔获IS要求或最终不执行。(这已在IE8 btw中修复)
scunliffe


非常真实 可能值得强调语言。我相信.net不错
MPritchard

7

只有在确实存在异常的情况下才应抛出异常-发生超出正常范围的事情。一个空的catch块基本上说“正在发生坏事,但我不在乎”。这是一个坏主意。

如果您不想处理该异常,请使其向上传播,直到到达可以处理该异常的代码为止。如果没有什么可以处理该异常,则应将应用程序关闭。


3
有时您知道它不会影响任何事情。然后继续进行操作,并对其进行评论,以便下一个家伙不仅仅因为您不称职而认为您搞砸了。
David Thornley,2009年

是的,所有的事情都有时间和地点。不过,总的来说,我认为空的catch块是该规则的例外-在极少数情况下,您想真正忽略一个例外(通常是IMO,这是错误使用例外的结果)。
里德·科普西

2
@David Thornley:如果您至少不检查异常以确定它是预期的无意义的错误还是应该向上传播的错误,您怎么知道它不会有任何影响?
戴夫·谢罗曼

2
@Dave:虽然这catch (Exception) {}是一个坏主意,但catch (SpecificExceptionType) {}可能很好。程序员DID使用catch子句中的类型信息检查异常。
Ben Voigt 2013年

7

我认为,如果您捕获到一个特定的异常类型就可以了,而您知道只针对某个特定的异常类型会引发异常原因该异常,并且您期望该异常并且真的不需要执行任何操作。

但是即使在这种情况下,调试消息也可能是正确的。



3

一个空的catch块实际上是在说:“我不想知道抛出什么错误,我将忽略它们。”

它与VB6的相似On Error Resume Next,除了抛出异常后try块中的所有内容都将被跳过。

当某些东西随后破裂时,这无济于事。


我实际上会说“ On Error Resume Next”更糟-无论如何,它只会尝试下一行。空捕获将跳转以通过try语句的结尾括号
MPritchard

好点子。我可能应该在答复中指出这一点。
Powerlord

1
这不是完全正确,如果您有一个具有特定异常类型的空try ... catch,则它将仅忽略这种类型的异常,并且这可能是合法的……
Meta-Knight

但是,如果您知道正在抛出特定类型的异常,则似乎您有足够的信息来编写逻辑,从而避免使用try-catch来处理这种情况。
斯科特·劳伦斯

@Scott:某些语言(例如Java)已经检查了异常...您不得不为其编写catch块的异常。
Powerlord

3

这与“不要使用异常来控制程序流”和“仅在特殊情况下使用异常”相辅相成。如果这些都完成了,那么只有在出现问题时才应发生异常。而且,如果有问题,您也不想默默地失败。在不需要处理问题的罕见异常中,至少应记录该异常,以防万一异常不再是异常。比失败更糟糕的是,默默地失败。


2

我认为完全空的catch块是一个坏主意,因为无法推断忽略异常是代码的预期行为。吞下异常并在某些情况下返回false或null或其他某个值并不一定很坏。.net框架具有许多以这种方式运行的“尝试”方法。根据经验,如果吞下异常,则在应用程序支持日志记录的情况下添加注释和日志语句。


1

因为如果一个异常抛出你永远不会看到它-默默失败是最糟糕的选择-你会得到错误的行为,不知道该看哪里,它的发生。至少在这里放一条日志消息!即使这是“永远不会发生”的事情!


1

空的catch块表示程序员不知道如何处理异常。它们抑制了异常的爆发并被另一个try块正确处理。总是尝试着做一些事情,但要抓住的例外。


1

我发现空的catch语句最让人讨厌的是其他程序员在何时执行此操作。我的意思是,当您需要从其他人调试代码时,任何空的catch语句都会使这种工作变得比原来更加困难。恕我直言,catch语句应始终显示某种错误消息-即使未处理该错误,它也应至少检测到该错误消息(仅在调试模式下才可用)


1

这可能永远都不是对的,因为您正在悄悄地传递所有可能的异常。如果您期望某个特定的例外,则应该对其进行测试,如果不是您的例外,请重新抛出该例外。

try
{
    // Do some processing.
}
catch (FileNotFound fnf)
{
    HandleFileNotFound(fnf);
}
catch (Exception e)
{
    if (!IsGenericButExpected(e))
        throw;
}

public bool IsGenericButExpected(Exception exception)
{
    var expected = false;
    if (exception.Message == "some expected message")
    {
        // Handle gracefully ... ie. log or something.
        expected = true;
    }

    return expected;
}

1
这并不是您建议人们在那里使用的标准模式。大多数人只会捕捉他们所期望的任何类型的异常,而不是他们所不期望的异常。在某些情况下,我认为您的方法是有效的,请小心在这样的论坛中发布,因为这样一来,人们倾向于阅读此类内容,因为他们一开始并不了解;)
MPritchard,2009年

我的目的不是IsKnownException()正在检查异常的类型(是的,您应该通过不同的catch块来执行此操作),而是检查它是否是预期的异常。我将进行编辑以使其更加清晰。
xanadont

我最初是在考虑COMExceptions。您可以测试特定的ErrorCode并处理所需的代码。在用ArcOjbects编程时,我经常这样做。
xanadont

2
-1根据异常消息在代码中做出决定是一个非常糟糕的主意。确切的消息很少记录在案,并且随时可能更改。这是一个实现细节。或者消息可能基于可本地化的资源字符串。无论如何,它只能被人类阅读。
Wim Coenen,2009年

真是的 exception.Message可能已本地化,因此不是您所期望的。这是错误的。
frankhommers

1

通常,您应该只捕获可以实际处理的异常。这意味着在捕获异常时要尽可能具体。捕获所有异常很少是一个好主意,而忽略所有异常几乎总是一个非常不好的主意。

我只能想到少数情况下,空的catch块有一些有意义的用途。如果通过重新尝试该动作来“处理”正在捕获的特定异常,则无需在catch块中执行任何操作。但是,记录发生异常的事实仍然是一个好主意。

另一个示例:CLR 2.0更改了终结器线程上未处理异常的处理方式。在2.0之前的版本中,该过程可以保留下来。在当前CLR中,如果终结器线程上有未处理的异常,则该过程终止。

请记住,只有在确实需要finalizer的情况下,才应实现finalizer,即使那样,您也应在finalizer中做尽可能少的事情。但是,如果终结器必须执行的任何工作都可能引发异常,则需要在两种弊端中取较小的一种。您是否由于未处理的异常而关闭应用程序?还是要在某种程度上处于未定义状态?至少从理论上讲,在某些情况下,后者可能是两个弊端中较小的一个。在那种情况下,空的catch块将阻止进程终止。


1
我的意思是,例如,有时您想从某个地方(Web服务,数据库)获得一些其他信息,而您实际上并不在乎是否会获得此信息。因此,您尝试获取它,并且如果发生任何事情,那没关系,我只需添加一个“ catch(忽略了异常){}”,仅此而已

因此,以您的示例为例,在这种情况下,这是一个坏主意,因为您正在捕获并忽略所有异常。如果您只是捕捉EInfoFromIrrelevantSourceNotAvailable而忽略了它,那很好,但事实并非如此。您也忽略了ENetworkIsDown,这可能重要也可能不重要。您忽略了ENetworkCardHasMeltedEFPUHasDecidedThatOnePlusOneIsSeventeen,这几乎可以肯定是很重要的。

如果将空的catch块设置为仅捕获(并忽略)您不重要的某些类型的异常,则不是问题。最好是抑制并静默忽略所有异常,而不必先检查它们是否是预期的/正常的/无关的情况,这是非常罕见的。


1

在某些情况下,您可能会使用它们,但是这种情况很少出现。我可能会使用的一种情况包括:

  • 异常记录 根据上下文,您可能需要未处理的异常或消息。

  • 循环出现技术状况,例如渲染或声音处理或列表框回调,其中行为本身将说明问题,抛出异常将阻碍您的工作,而记录异常可能只会导致1000条“失败到XXX”消息。

  • 无法失败的程序,尽管它们至少仍应记录某些内容。

对于大多数winforms应用程序,我发现对于每个用户输入都只有一个try语句就足够了。我使用以下方法:(AlertBox只是一个快速的MessageBox.Show包装器)

  public static bool TryAction(Action pAction)
  {
     try { pAction(); return true; }
     catch (Exception exception)
     {
        LogException(exception);
        return false;
     }
  }

  public static bool TryActionQuietly(Action pAction)
  {
     try { pAction(); return true; }
     catch(Exception exception)
     {
        LogExceptionQuietly(exception);
        return false;
     }
  }

  public static void LogException(Exception pException)
  {
     try
     {
        AlertBox(pException, true);
        LogExceptionQuietly(pException);
     }
     catch { }
  }

  public static void LogExceptionQuietly(Exception pException)
  {
     try { Debug.WriteLine("Exception: {0}", pException.Message); } catch { }
  }

然后,每个事件处理程序都可以执行以下操作:

  private void mCloseToolStripMenuItem_Click(object pSender, EventArgs pEventArgs)
  {
     EditorDefines.TryAction(Dispose);
  }

要么

  private void MainForm_Paint(object pSender, PaintEventArgs pEventArgs)
  {
     EditorDefines.TryActionQuietly(() => Render(pEventArgs));
  }

从理论上讲,您可以使用TryActionSilently,它可能更适合呈现调用,以便异常不会生成无数的消息。



0

您永远不应有一个空的catch块。这就像隐藏您知道的错误。至少您应该在日志文件中写一个例外,以备不时之需。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.