为什么空的catch块是一个坏主意?
我刚刚看到一个关于try-catch的问题 ,哪些人(包括Jon Skeet)认为空的catch块是非常糟糕的主意? 为什么这个? 有没有空的渔获不是错误的设计决定?
我的意思是,例如,有时你想从某处(web服务,数据库)得到一些额外的信息,你真的不在乎你是否会得到这个信息。 所以你试着去得到它,如果有什么事情发生,没关系,我只需要添加一个“catch(Exception ignored){}”,这就是全部
通常,空的try-catch是一个糟糕的主意,因为你正在默默地吞下一个错误条件,然后继续执行。 有时候这可能是正确的做法,但通常这是一个开发人员看到异常,不知道该怎么做的标志,所以用空的方法来解决问题。
这是将黑色胶带放在发动机警告灯上的编程。
我相信你如何处理异常取决于你正在工作的软件的哪一层: 雨林中的例外 。
总的来说 ,这是一个糟糕的主意,因为这是一个非常罕见的情况,在这种情况下,一个失败(例外情况,更一般地说)得到适当的回应,没有任何回应。 最重要的是,空的catch
块是人们使用异常引擎进行错误检查的一个常用工具,他们应该先发制人。
说这总是不好的是不真实的…这是真的很少。 可能会出现以下情况:您不在乎是否存在错误,或者错误的存在表明您无论如何都无能为力(例如,将以前的错误写入文本日志文件,你得到一个IOException
,这意味着你不能写出新的错误)。
我甚至不会说,使用空的catch块的人是一个糟糕的程序员,不知道他在做什么。
如有必要,我使用空的catch块。 有时我正在使用的图书馆程序员不知道自己在做什么,即使在没有人需要的情况下也会抛出异常。
例如,考虑一些http服务器库,如果服务器抛出异常,我可以不在乎,因为客户端已经断开连接, index.html
不能发送。
有少数情况下可以证明是合理的。 在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
虽然在这种情况下,我承认记录失败的地方可能是一个改进。
空的catch块通常被放入,因为编码器不知道他们在做什么。 在我的组织里,一个空白的catch块必须包含一个评论,说明为什么什么都不做的例外是个好主意。
在相关说明中,大多数人不知道try {}块可以跟在catch {}或者finally {}之后,只需要一个。
只有在确实存在例外的情况下,才会抛出异常 – 超出正常范围的情况。 一个空的catch块基本上说“有什么不好的事情发生,但我只是不在乎”。 这是一个坏主意。
如果你不想处理这个异常,让它向上传播,直到它达到一些可以处理它的代码。 如果没有任何东西可以处理这个异常,它应该把应用程序关闭。
我认为,如果你捕捉到一个特定的异常类型,你知道这个异常类型只是出于某个特定的原因而被提出,那么你可以期待这个异常,而且真的不需要做任何事情。
但即使在这种情况下,调试消息也可能是有序的。
Per Josh Bloch – Item 65:不要忽视 有效Java的 例外 :
- 一个空的catch块阻止异常的目的
- 至少,catch块应该包含一个注释,解释为什么忽略这个异常是合适的。
一个空的catch块本质上是说:“我不想知道抛出了什么错误,我只是忽略它们”。
它类似于VB6的On Error Resume Next
,除了在引发异常之后的try块中的任何内容都将被跳过。
当某些事情中断时,这并没有帮助。
这与“不要使用异常来控制程序流程”并且“仅在特殊情况下使用异常”是一致的。 如果这些都完成了,那么只有在有问题时才会发生异常。 如果出现问题,你不想失败。 在罕见的异常情况下,没有必要处理这个问题,你至少应该记录异常情况,以防万一异常变成异常。 唯一比失败更糟的是默默地失败。
我认为一个完全空的catch块是一个坏主意,因为没有办法推断忽略异常是代码的预期行为。 在某些情况下吞下异常并返回false或null或其他值并不一定是坏事。 .net框架有许多“尝试”方法,这样的行为。 作为一个经验法则,如果你吞下一个异常,如果应用程序支持日志,添加一个注释和一个日志语句。
因为如果抛出一个异常,你永远不会看到它 – 默默地失败是最糟糕的选择 – 你会得到错误的行为,不知道发生在哪里。 至少在那里写一条日志消息! 即使这是“永远不会发生的事情”!
空的catch块表示程序员不知道如何处理异常。 他们正在抑制可能冒泡的异常,并被另一个try块正确处理。 总是尝试和你正在捕捉的例外做一些事情。
我发现最让人讨厌的是,当一些其他的程序员做到这一点时,空的catch语句是最令人讨厌的。 我的意思是,当你需要从别人那里调试代码时,任何空的catch语句都会使得这个任务变得更加困难。 恕我直言,catch语句应该总是显示某种错误信息 – 即使错误没有得到处理,它应该至少检测到它(只在调试模式下)
这可能不是正确的,因为你默默地传递每一个可能的例外。 如果你有一个特定的异常,那么你应该测试它,重新抛出,如果它不是你的例外。
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块有一些有意义的目的的例子。 如果有什么特别的例外,你捕捉是“处理”只是重新尝试的行动,将不需要在catch块中做任何事情。 但是,记录发生异常的事实仍然是一个好主意。
另一个例子:CLR 2.0改变了终结器线程上未处理的异常的处理方式。 2.0之前,这个过程被允许在这种情况下生存。 在当前的CLR中,如果终结器线程上出现未处理的异常,则终止该进程。
请记住,如果你真的需要一个终结器,你应该只实现一个终结器,即使如此,你应该尽可能在终结器中做一些事情。 但是,如果终结者必须做的工作可能会抛出一个异常,你需要选择两个邪恶中较小的一个。 由于未处理的异常,您是否要关闭应用程序? 或者你想在一个或多或少未定义的状态? 至少从理论上讲,在某些情况下,后者可能是两个邪恶中较小的一个。 在这种情况下,空的catch块会阻止进程被终止。
我的意思是,例如,有时你想从某处(web服务,数据库)得到一些额外的信息,你真的不在乎你是否会得到这个信息。 所以你试着去得到它,如果有什么事情发生,没关系,我只需要添加一个“catch(Exception ignored){}”,这就是全部
所以,以你的榜样为例,在这种情况下这是一个糟糕的主意,因为你在捕捉和忽略所有的异常。 如果您仅捕获EInfoFromIrrelevantSourceNotAvailable
并忽略它,那EInfoFromIrrelevantSourceNotAvailable
,但是您没有。 你也忽略了ENetworkIsDown
,这可能也可能不重要。 你忽略了ENetworkCardHasMelted
和EFPUHasDecidedThatOnePlusOneIsSeventeen
,这几乎肯定是重要的。
如果一个空的catch块被设置为只捕获(并忽略)某些你认为不重要的类型的异常,那么它就不是问题。 在这种情况下,抑制和默默地忽略所有的例外情况,而不停止首先检查它们是否是预期的/正常的/不相关的情况是非常罕见的。
如果你不知道在catch块中做什么,你可以只记录这个异常,但不要留空白。
try { string a = "125"; int b = int.Parse(a); } catch (Exception ex) { Log.LogError(ex); }
你永远不应该有一个空的catch块。 这就像隐藏你所知道的一个错误。 至少你应该写出一个例外的日志文件,稍后检查,如果你按时间。
有些情况下你可能会使用它们,但它们应该是非常罕见的。 我可以使用的情况包括:
-
异常记录; 取决于上下文,您可能需要发布未处理的异常或消息。
-
循环的技术情况,如渲染或声音处理或列表框回调,其中行为本身将显示问题,抛出异常将阻碍,并记录异常可能只会导致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,这对渲染调用可能会更好,这样一个异常不会产生大量的消息。