捕获System.Exception等非特定异常是不好的做法?为什么?

时间:2009-01-08 22:44:24

标签: c# .net exception exception-handling

我目前正在进行代码审查,以下代码让我跳了起来。我看到这个代码存在多个问题。你是否同意我的观点?如果是这样,我该如何向我的同事解释这是错误的(顽固型......)?

  • 抓住一般异常(Exception ex)
  • 使用“if(ex is something)”而不是使用另一个catch块
  • 我们吃SoapException,HttpException和WebException。但是如果Web服务失败了,那就没那么多了。

代码:

try
{
    // Call to a WebService
}
catch (Exception ex)
{
    if (ex is SoapException || ex is HttpException || ex is WebException)
    {
        // Log Error and eat it.
    }
    else
    {
        throw;
    }
}

8 个答案:

答案 0 :(得分:14)

咒语是:

  • 你应该只捕获例外情况 你可以妥善处理它们

因此:

  • 你不应该抓到一般 例外。

在你的情况下,是的,你应该抓住那些例外并做一些有用的事情(可能不仅仅是吃它们 - 你可以在记录它们之后throw)。

您的程序员正在使用 good throw(不是throw ex)。

这是您可以捕获多个特定异常的方法:

try
{
    // Call to a WebService
}
catch (SoapException ex)
{
    // Log Error and eat it
}
catch (HttpException ex)
{
    // Log Error and eat it
}
catch (WebException ex)
{
    // Log Error and eat it
}

这几乎与您的代码相同。您的dev可能就是这样做的,以避免重复“日志错误并吃掉它”块。

答案 1 :(得分:7)

  

我目前正在进行代码审查,以下代码让我跳了起来。我看到这个代码存在多个问题。你同意我的意见吗?

不完全,见下文。

  
      
  • 抓住一般异常(Exception ex)
  •   

一般来说,只要你重新抛出它(带有throw;),当你得出你无法处理它的结论时,捕获一般异常实际上是可以的。代码就是这样,所以这里没有直接的问题。

  
      
  • 使用“if(ex is something)”而不是使用另一个catch块
  •   

catch块的净效果是实际上只处理了SoapException,HttpException等,并且所有其他异常都在调用堆栈中传播。我认为这是代码应该做的功能,所以这里也没有问题。

然而,从美学与美学可读性POV我更喜欢多个catch块到“if(ex是SoapException || ..)”。一旦将公共处理代码重构为方法,多个catch块只会稍微打字,并且对于大多数开发人员来说更容易阅读。而且,最后一击很容易被忽视。

  
      
  • 我们吃SoapException,HttpException和WebException。但是如果Web服务失败了,那就没那么多了。
  •   

这里可能潜伏着代码的最大问题,但是在不了解应用程序性质的情况下很难提供建议。如果Web服务调用正在执行您以后依赖的操作,那么仅记录和调用可能是错误的。吃异常。通常,您将异常传播给调用者(通常在将其包装到例如XyzWebServiceDownException之后),甚至可能在重试webservice调用几次之后(在存在虚假网络问题时更加健壮)。

答案 2 :(得分:6)

捕获和重新抛出相同异常的问题在于,虽然.NET尽最大努力保持堆栈跟踪完好无损,但它总是最终被修改,这可以使追踪实际上来自更加困难的地方(例如,异常行号可能看起来是重新抛出语句的行,而不是最初引发异常的行。 There's a whole load more information about the difference between catch/rethrow, filtering, and not catching here.

当存在这样的重复逻辑时,你真正需要的是一个异常过滤器,所以你只能捕获你感兴趣的异常类型.VB.NET有这个功能,但不幸的是C#没有。假设的语法可能如下所示:

try
{
    // Call to a WebService
}
catch (Exception ex) if (ex is SoapException || ex is HttpException || /* etc. */)
{
    // Log Error and eat it
}

虽然你不能这样做,但我倾向于使用lambda表达式作为公共代码(你可以在C#2.0中使用delegate),例如。

Action<Exception> logAndEat = ex => 
    {  
        // Log Error and eat it
    };

try
{
    // Call to a WebService
}
catch (SoapException ex)
{
    logAndEat(ex);
}
catch (HttpException ex)
{
    logAndEat(ex);
}
catch (WebException ex)
{
    logAndEat(ex);
}

答案 3 :(得分:2)

有时这只是为了处理“捕获每个异常”场景,而不是实际捕获每个异常。

我认为,有时候,低级框架/运行时代码需要确保没有异常从中转义。不幸的是,框架代码也无法知道线程执行的代码引发了哪些异常。

在这种情况下,可能的catch块可能如下所示:

try
{
   // User code called here
}
catch (Exception ex)
{
   if (ExceptionIsFatal(ex))
     throw;

   Log(ex);
}

然而,这里有三个要点:

  1. 这不适用于所有情况。在代码审查中,我们非常挑剔那些实际上是必要的地方,因此允许这样做。
  2. ExceptionIsFatal()方法确保我们不会吃掉永远不会被吞下的异常(ExecutionEngineException,OutOfMemoryException,ThreadAbortException等)。
  3. 仔细记录被吞下的内容(event-log,log4net,YMMV)
  4. 通常情况下,我都是通过终止CLR让未被捕获的异常简单地“崩溃”应用程序的做法。但是,特别是在服务器应用程序中,这有时是不明智的。如果一个线程遇到一个被认为是非致命的问题,那么没有理由将整个过程扯掉,从而杀死所有其他正在运行的请求(例如,WCF以这种方式处理某些情况)。

答案 4 :(得分:2)

我想在此处添加,因为几乎所有java / C#代码中的异常处理都是错误的。即你最终会因为忽略异常而很难调试错误,或者同样糟糕的是,你会得到一个模糊的异常,它不会告诉你什么,因为盲目地跟着“捕获(异常)是坏的”而事情就更糟了。

首先,要了解异常是一种促进跨代码层返回错误信息的方法。现在,错误1:一个层不仅仅是一个堆栈帧,一个层是具有明确定义的责任的代码。如果你只是编码接口和impl只是因为,你有更好的东西要修复。

如果图层设计得很好并且具有特定的职责,那么错误的信息在起泡时具有不同的含义。 &lt; -t这是做什么的关键,没有普遍的规则。

因此,这意味着当发生异常时,您有2个选项,但您需要了解图层中的位置:

A)如果你处于一个层的中间,而你只是一个内部的,通常是私有的帮助函数,那就会变坏:不要担心,让调用者接收异常。它非常好,因为你没有业务背景和 1)你没有忽略错误和 2)调用者是你的图层的一部分,应该知道这可能发生,但你现在可能不会在下面处理它。

或......

B)你是图层的顶部边界,是内部的立面。然后,如果你得到一个异常,那么默认值应该是CATCH ALL并且阻止任何特定的异常进入上层,这对调用者没有意义,或者更糟糕的是,你可能会改变,调用者将依赖于一个实现细节,两者都会破裂。

应用程序的强度是层之间的解耦级别。在这里,您将作为一般规则停止所有操作,并使用一般异常重新抛出错误,将信息转换为上层的更有意义的错误。

规则:层的所有入口点都应使用CATCH ALL保护,并且所有错误都会被翻译或处理。现在这个'handeled'只发生1%的时间,大多数情况下你只需要(或者可以)以正确的抽象方式返回错误。

不,我确定这很难理解。真实的例子 - &gt;

我有一个包运行一些模拟。这些模拟是在文本脚本中。有一个包编译这些脚本,有一个通用的utils包只读取文本文件,当然还有基本的java RTL。 UML依赖性是 - &gt;

Simulator-&gt; Compiler-&gt; utilsTextLoader-&gt; Java文件

1)如果某个私有内的utils加载器出现问题,我会得到一个FileNotFound,Permissions或者什么,只是让它通过。你无能为力。

2)在边界处,在最初调用的utilsTextLoader函数中,您将遵循上述规则和CATCH_ALL。编译器不关心发生了什么,现在只需要加载文件是否已加载。因此,在catch中,重新抛出一个新的异常并将FileNotFound或其他任何内容转换为“无法读取文件XXXX”。

3)编译器现在知道未加载源。多数民众赞成需要知道的。因此,如果我稍后将utilsTestLoader更改为从网络加载,编译器将不会更改。如果你放弃FileNotFound并稍后更改,你将不会影响编译器。

4)循环重复:调用文件的下层的实际函数在获取异常时不会执行任何操作。所以它让它上升。

5)当异常到达模拟器和编译器之间的层时,编译器再次CATCHES_ALL,隐藏任何细节并抛出更具体的错误:“无法编译脚本XXX”

6)最后再重复一次循环,调用编译器的模拟器函数就可以了。

7)最后的边界是给用户的。用户是LAYER,全部适用。主要尝试catches_ALL,最后只是创建一个漂亮的对话框或页面,并向用户“抛出”翻译错误。

所以用户看到了。


模拟器:致命错误无法启动模拟器

- 编译器:无法编译脚本FOO1

- TextLoader:无法读取文件foo1.scp

--- trl:FileNotFound


比较:

a)编译器:NullPointer异常&lt; -common案例和丢失的夜晚调试文件名错误

b)Loader:找不到文件&lt; - 我是否提到加载程序加载了数百个脚本?

c)没有任何事情发生,因为所有人都被忽略了!!!

当然,这假设在每次重新抛出时你都不会忘记设置原因异常。

我的2cts。这个简单的规则多次挽救了我的生命......

-Ale

答案 5 :(得分:1)

您通常应该在全局处理程序中捕获泛型异常(这是记录意外异常的最佳位置),但如前所述,如果您计划对其进行某些操作,则应该只捕获其他位置的特定异常类型。 catch块应该显式查找这些异常类型,而不是您发布的代码。

答案 6 :(得分:0)

在这种情况下我不认为这是一件坏事,但我也在我的代码中做了类似的事情,可以安全地忽略被捕获的异常,其余的被重新抛出。正如Michael's response所指出的那样,将每个catch作为一个单独的块可能会导致一些可读性问题,这些问题可以通过这条路径来防止。

关于向你的同事指出这一点,我认为你很难说服他们这是错误的做事方式 - 如果他们是顽固的话更是如此 - 因为做事的潜在可读性问题另一种方法。由于这个版本仍然抛出无法处理的通用异常,因此它符合实践的精神。但是,如果代码符合以下条件:

try
{
  // Do some work
}
catch (Exception ex)
{
  if (ex is SoapException)
  {
    // SoapException specific recovery actions
  }
  else if (ex is HttpException)
  {
    // HttpException specific recovery actions
  }
  else if (ex is WebException)
  {
    // WebException specific recoery actions
  }
  else
  {
    throw;
  }
}

然后我认为你会有更多的理由担心,因为通过在一般的异常块中检查它来为特定异常做工作是没有意义的。

答案 7 :(得分:0)

princeple只是为了捕捉你可以处理的异常。 例如,如果你知道如何处理findnotfound,你会抓住filenotfoundexception,其他的,不要抓住它并让它被抛到上层。