为什么空捕获块是个坏主意?

我刚刚看到一个关于接球的问题,人们(包括Jon Skeet)说空捕捉块是一个非常糟糕的主意?为什么呢?在任何情况下,空接点都不是错误的设计决策吗?

我的意思是,例如,有时你想从某个地方(webservice,数据库)获得一些额外的信息,你真的不关心你是否会得到这个信息。所以你试着获取它,如果发生了什么,没关系,我只会添加一个“catch (Exception ignored){}”,仅此而已

137562 次浏览

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

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

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

这就相当于在引擎警示灯上贴上黑色胶带。

我相信你如何处理异常取决于你工作在软件的哪一层:热带雨林的例外

他们是一个坏主意在一般情况下,因为这是一个真正罕见的情况下,一个失败(异常情况,更一般地说)是适当地遇到没有任何响应。最重要的是,空catch块是使用异常引擎进行错误检查的人使用的常用工具,他们应该先发制人。

总是不好是不真实的…这种情况很少发生。在某些情况下,你不关心是否有错误,或者错误的存在以某种方式表明你无法对它做任何事情(例如,当将以前的错误写入文本日志文件时,你得到IOException,这意味着你无论如何都不能写出新的错误)。

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

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

如果有东西坏了,这也没用。

只有在确实出现异常时才应该抛出异常——发生了超出正常范围的事情。一个空的catch块基本上表示“一些不好的事情正在发生,但我只是不在乎”。这是个坏主意。

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

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

但即使在这种情况下,也可能需要调试消息。

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

空捕获块表示程序员不知道如何处理异常。它们正在抑制异常可能出现的冒泡,并由另一个try块正确处理。总是尝试做一些你正在捕捉的例外。

这可能从来都不是正确的事情,因为你默默地传递每一个可能异常。如果有您期望的特定异常,那么您应该测试它,如果它不是您的异常则重新抛出。

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;
}

我不会把事情延伸到说使用空catch块的人是一个糟糕的程序员,不知道他在做什么……

如果需要,我使用空捕捉块。有时我正在使用的库的程序员不知道他在做什么,甚至在没有人需要的情况下抛出异常。

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

我发现最令人讨厌的空catch语句是其他程序员做的。我的意思是,当您需要调试别人的代码时,任何空的catch语句都会使这种工作变得更加困难。恕我直言,catch语句应该总是显示某种错误消息——即使错误没有被处理,它至少应该检测到它(只在调试模式下启用)

在极少数情况下,这样做是合理的。在Python中,你经常会看到这样的结构:

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

所以它可能是OK的(取决于你的应用程序):

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

尽管在这种情况下,我承认在某个地方记录失败可能是一种改进。

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

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

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

我只能想到一些空catch块具有有意义的用途的实例。如果您正在捕获的任何特定异常都是通过重新尝试操作来“处理”的,则不需要在catch块中做任何事情。但是,记录发生异常的事实仍然是一个好主意。

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

请记住,只有在确实需要终结器时才应该实现终结器,即使这样,也应该在终结器中尽可能少地执行一些操作。但是,如果终止器必须做的任何工作都可能抛出异常,则需要在两害相权取其轻。是否因为未处理的异常而关闭应用程序?或者您希望在一种或多或少未定义的状态下进行?至少在理论上,后者在某些情况下可能是两害相权取其轻。在这种情况下,空的catch块将防止进程终止。

我的意思是,例如,有时你想从某个地方(webservice,数据库)获得一些额外的信息,你真的不关心你是否会得到这个信息。所以你试着获取它,如果发生了什么,没关系,我只会添加一个“catch (Exception ignored){}”,仅此而已

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

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

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

在某些情况下,您可能会使用它们,但它们应该非常不频繁。我可能会使用它的情况包括:

  • 异常日志记录;根据上下文的不同,您可能希望发布一个未处理的异常或消息。

  • 循环技术情况,如渲染或声音处理或列表框回调,行为本身将演示问题,抛出异常将只是阻碍,记录异常可能只会导致1000个“失败到XXX”消息。

  • 的程序不能失败,尽管它们至少应该记录一些东西。

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

  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));
}

理论上,您可以使用tryactionsilent,它可能更适合呈现调用,这样异常就不会生成无穷无尽的消息。

如果你不知道在catch block中要做什么,你可以只记录这个异常,但不要让它空着。

        try
{
string a = "125";
int b = int.Parse(a);
}
catch (Exception ex)
{
Log.LogError(ex);
}

根据有效Java乔什•布洛赫 - 第65项:不要忽略异常:

  1. 空的catch块无法实现异常的目的
  2. 至少,catch块应该包含一个注释,解释为什么忽略异常是合适的。