204

我刚刚看到一个关于 try-catch的问题,有人(包括 Jon Skeet)说空的 catch 块是一个非常糟糕的主意?为什么这个?是否没有空接不是错误的设计决策的情况?

我的意思是,例如,有时您想从某个地方(网络服务、数据库)获取一些额外的信息,而您真的不在乎是否会获得这些信息。因此,您尝试获取它,如果发生任何事情,那没关系,我只需添加一个“catch(忽略异常){}”就可以了

4

20 回答 20

309

通常空的 try-catch 是一个坏主意,因为您正在默默地吞下一个错误条件,然后继续执行。有时这可能是正确的做法,但通常这表明开发人员看到了异常,不知道如何处理它,因此使用空捕获来解决问题。

这相当于在发动机警告灯上贴上黑色胶带。

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

于 2009-08-05T16:34:31.320 回答
37

一般来说,它们是一个坏主意因为它是一种真正罕见的情况,其中失败(更一般的例外情况)被适当地满足而没有任何响应。最重要的是,空catch块是使用异常引擎进行错误检查的人们常用的工具,他们应该先发制人地进行。

说它总是很糟糕是不真实的……那很​​少是真实的。在某些情况下,您可能不在乎是否存在错误,或者错误的存在以某种方式表明您无论如何都无法对其执行任何操作(例如,将先前的错误写入文本日志文件和你得到一个IOException,这意味着你无论如何都不能写出新的错误)。

于 2009-08-05T16:35:10.350 回答
11

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

如有必要,我会使用空的 catch 块。有时我正在使用的库的程序员不知道他在做什么,即使在没有人需要它的情况下也会抛出异常。

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

于 2009-08-05T16:58:35.843 回答
9

仅当确实存在异常时才应抛出异常 - 发生超出规范的事情。一个空的 catch 块基本上说“发生了一些不好的事情,但我不在乎”。这是一个坏主意。

如果你不想处理异常,让它向上传播,直到它到达一些可以处理它的代码。如果没有任何东西可以处理异常,它应该关闭应用程序。

于 2009-08-05T16:35:27.043 回答
9

我认为如果你捕获了一个特定的异常类型,你知道它只会因为一个特定的原因而引发,并且你期望这个异常并且真的不需要对它做任何事情,那是可以的。

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

于 2009-08-05T16:35:55.637 回答
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

尽管即使在这种情况下,我承认在某处记录故障可能是一种改进。

于 2009-08-05T17:31:37.023 回答
7

通常会放入空的 catch 块,因为编码人员并不真正知道他们在做什么。在我的组织中,一个空的 catch 块必须包含一个注释,说明为什么对异常什么都不做是个好主意。

在相关说明中,大多数人不知道 try{} 块可以后跟 catch{} 或 finally{},只有一个是必需的。

于 2009-08-05T16:34:03.770 回答
7

Per Josh Bloch -第 65 条:不要忽略有效 Java的异常:

  1. 一个空的 catch 块破坏了异常的目的
  2. 至少,catch 块应该包含一个注释,解释为什么忽略异常是合适的。
于 2015-03-09T11:36:33.540 回答
3

一个空的 catch 块本质上是在说“我不想知道抛出了什么错误,我只想忽略它们。”

它类似于 VB6 的On Error Resume Next,除了抛出异常后 try 块中的任何内容都将被跳过。

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

于 2009-08-05T16:35:17.073 回答
3

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

于 2009-08-05T23:55:19.947 回答
2

空的 catch 块表示程序员不知道如何处理异常。他们正在抑制异常可能冒泡并被另一个 try 块正确处理。总是尝试做一些你发现的例外。

于 2009-08-05T16:37:25.823 回答
2

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

于 2009-08-05T18:13:26.237 回答
2

如果您不知道在 catch 块中该做什么,您可以只记录此异常,但不要将其留空。

        try
        {
            string a = "125";
            int b = int.Parse(a);
        }
        catch (Exception ex)
        {
            Log.LogError(ex);
        }
于 2013-09-12T08:00:17.213 回答
1

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

于 2009-08-05T16:36:05.497 回答
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;
}
于 2009-08-05T16:37:40.550 回答
1

我发现空的 catch 语句最烦人的是其他程序员这样做的时候。我的意思是,当您需要从其他人那里调试代码时,任何空的 catch 语句都会使这项工作变得更加困难。恕我直言,catch 语句应始终显示某种错误消息-即使未处理错误,它至少应检测到它(仅在调试模式下启用)

于 2009-08-05T17:01:22.793 回答
1

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

我只能想到几个例子,其中一个空的 catch 块有一些有意义的目的。如果您捕获的任何特定异常是通过重新尝试操作来“处理”的,则无需在 catch 块中执行任何操作。但是,记录异常发生的事实仍然是一个好主意。

另一个例子:CLR 2.0 改变了处理终结器线程上未处理异常的方式。在 2.0 之前,该过程被允许在这种情况下生存。在当前 CLR 中,如果终结器线程上出现未处理的异常,则进程将终止。

请记住,只有在确实需要终结器时才应该实现终结器,即便如此,您也应该在终结器中尽可能多地做一些事情。但是,如果你的终结器必须做的任何工作都可能引发异常,那么你需要在两个弊端中取其较小者。由于未处理的异常,您要关闭应用程序吗?或者你想在一个或多或少未定义的状态下进行?至少在理论上,后者在某些情况下可能是两害相权取其轻。在这种情况下,空的 catch 块将阻止进程终止。

于 2009-08-07T11:37:13.627 回答
1
我的意思是,例如,有时您想从某个地方(网络服务、数据库)获取一些额外的信息,而您真的不在乎是否会获得这些信息。因此,您尝试获取它,如果发生任何事情,那没关系,我只需添加一个“catch(忽略异常){}”就可以了

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

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

于 2009-08-07T12:16:39.783 回答
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,这可能更适合呈现调用,这样异常就不会生成无穷无尽的消息。

于 2012-01-19T00:53:54.460 回答
0

你永远不应该有一个空的 catch 块。这就像隐藏一个你知道的错误。至少您应该在日志文件中写出一个例外,以便在时间紧迫时稍后查看。

于 2009-12-11T02:24:25.277 回答