.NET捕获常规异常

6

.NET编程指南规定我们不应该捕获通用异常。我认为以下代码不太好,因为它使用了通用异常类型的捕获:

    private object CreateObject(string classname)
    {
        object obj = null;
        if (!string.IsNullOrEmpty(classname))
        {
           try
           {
              System.Type oType = System.Type.GetTypeFromProgID(customClass);
              obj = System.Activator.CreateInstance(oType);
           }
           catch (Exception ex)
           {
               Log.Error("Unable to create instance for COM Object with class name " + classname + "\n" + ex.Message);
           }
        }
        return obj;
    }

在以下代码中,我捕获了特定的异常,但并非全部异常,如果异常与非泛型异常不同,则重新抛出异常。然而,函数“CreateInstance”可能会引发许多异常(ArgumentNullException、ArgumentException、NotSupportedException、TargetInvocationException、MethodAccessException、MemberAccessException、InvalidComObjectException、MissingMethodException、COMException、TypeLoadException)。
是否可以捕获所有其他单个异常?还是有更好的方法?
    private object CreateObject(string classname)
    {
        object obj = null;
        if (!string.IsNullOrEmpty(classname))
        {
           try
           {
              System.Type oType = System.Type.GetTypeFromProgID(customClass);
              obj = System.Activator.CreateInstance(oType);
           }
           catch (NotSupportedException ex)
           {
              Log.Error("...." + ex.Message);
           }
           catch (TargetInvocationException ex)
           {
              Log.Error("...." + ex.Message);
           }
           catch (COMException ex)
           {
              Log.Error("...." + ex.Message);
           }
           catch (TypeLoadException ex)
           {
              Log.Error("...." + ex.Message);
           }
           catch (InvalidComObjectException ex)
           {
              Log.Error("...." + ex.Message);
           }
           catch (Exception ex)
           {
               Log.Error("Unable to create instance for COM Object with class name " + classname + "\n" + ex.Message);
               throw;
           }
        }
        return obj;
    }

有没有忽略已捕获异常的正当理由? - user151323
7个回答

12

一般情况下,除非:

  1. 你有一个特定的异常可以处理,并且你可以对其进行解决。但在这种情况下,你应该始终检查是否应该首先考虑解决并避免异常。

  2. 你处于应用程序的顶层(例如UI)并且不希望向用户显示默认行为。例如,你可能希望出现一个带有“请将日志发送给我们”的错误对话框。

  3. 你在处理完异常后重新抛出它,例如,如果你回滚了一个数据库事务。

在这个例子中为什么要捕获所有这些不同类型的异常?在我看来,你的代码只需要是:

try
{
    System.Type oType = System.Type.GetTypeFromProgID(customClass);
    return System.Activator.CreateInstance(oType);
}
catch (Exception ex)
{
    Log.Error("...." + ex.Message);

    //the generic catch is always fine if you then do this:
    throw;
}

你的问题属于规则(3)的例子 - 你想记录一个异常,然后继续抛出它。

所有这些不同类型的异常都是为了在某些情况下处理异常(即情况1)。例如,假设您知道有一种未经管理的调用可以解决COMException,那么您的代码将变成:

try
{
    System.Type oType = System.Type.GetTypeFromProgID(customClass);
    return System.Activator.CreateInstance(oType);
}
catch (COMException cex)
{   //deal with special case:
    return LoadUnmanaged();
}
catch (Exception ex)
{
    Log.Error("...." + ex.Message);

    //the generic catch is always fine if you then do this:
    throw;
}

我捕获所有这些不同类型的异常,因为它们可能会发生(例如,用户可以传递一个不受支持或未正确注册的类名),在这种情况下,没有一种好的方法可以提前检查异常(就像我可以检查ArgumentNullException一样)。 - Ioannis
我相信所有这些异常都可能发生,但是除非你要针对特定的异常类型做一些具体的处理,否则最好只捕获通用的 Exception 并将其抛出。 - Keith
1
这个答案完全正确。关键是你将一般的异常向上抛出而不是吞噬它们。 - justin.m.chase

9

在.NET 2+框架中,捕获通用异常是可以接受的。

——编辑

唯一的原因是,如果您可以使用不同的异常进行不同的操作。 如果计划对所有异常进行相同处理,则只需捕获通用异常(或特定异常),并将其他任何异常传递。


3
我同意。有时候我觉得我们把不捕获一般异常这件事情太过极端了。 - David Basarab
6
我不同意。这样会使你的应用程序处于异常状态。你应该只处理你能够解决的异常情况。仅仅记录日志并继续执行是一个糟糕的想法。 - justin.m.chase
1
对于未知异常,记录并继续运行是一个不好的想法,因为你不知道异常有多严重。例如,如果遇到OutOfMemoryException会发生什么?你不应该只记录错误并继续运行。程序应该停止运行。与其试图让不稳定的程序继续运行,还不如“快速失败”。http://www.martinfowler.com/ieeeSoftware/failFast.pdf - Phil
2
@Noon 我同意这一点。然而,“如果您计划将它们全部处理相同,则只需捕获通用异常”可以被经验不足的程序员解释为可以捕获一般的System.Exception,做一些操作,然后继续执行。因此最好明确说明,如果您不知道正在处理哪种类型的错误,最好重新抛出或退出。 - Phil
话题发起者并没有做同样的事情。如果他使用通用catch,那么应该有if(ex is ... or ex is ...){} else {throw;} - Evgeny Gorbovoy
显示剩余4条评论

1

我认为捕获所有异常以拦截问题并向用户显示友好的消息,而不是一些可怕的堆栈输出是可以的。

只要你不仅吞噬异常,而是在后台记录它们并适当地对其做出反应。


1

如果您想对不同类型的异常执行特殊操作,则应在单独的catch块中捕获它们。否则将单独记录它们是毫无意义的。


1

并没有严格的规则说我们不应该使用一般性异常,但是指南表明,每当我们有处理特定类型的异常选项时,我们不应该选择通用异常。

如果我们确定所有异常都将以相同的方式处理,则使用通用异常,否则为每个特定的异常进行处理,并且对于一些未知异常,通用异常应该放在最后。

有时候,在应用程序中发生任何未经处理的异常,由于特定异常处理而导致应用程序崩溃。

因此,更好的方法是处理所有特定的异常,然后也给出通用异常的机会,以便应用程序保持稳定而不崩溃。

这些不需要的通用异常可以报告或记录在某个地方,以便将来改进应用程序版本。


1

在处理错误时,习惯性地捕获基本的Exception被认为是不良实践,因为它显示了您实际上正在处理什么的潜在缺乏理解。当您看到一段代码捕获Exception时,它所表示的意思是,“如果这里出了任何问题,请执行此操作”,其中“任何问题”可能从NullReferenceExceptionOutOfMemoryException都有可能。

将所有错误视为同等严重的危险在于,它暗示您不关心错误可能有多严重或如何解决错误。99%的情况下,当我看到catch(Exception ex)代码时,它紧接着的代码会吞掉异常,没有给出任何关于语句失败原因的线索。呃。

您提供的错误日志记录示例展示了使用基本Exception的正确方式,在需要真正将所有异常视为相同的情况下使用,通常是在应用程序的顶层,以防止应用程序以丑陋的混乱状态终止。


1

我同意Keith的答案。这两个代码示例的真正问题在于它们可能会返回null。您有一个名为CreateObject的方法,该方法返回类型为object的实例。由于某些原因(例如COMException),该方法失败了。然后,这两个代码示例将返回null。现在,调用代码需要负责检查结果与null的比较,否则它将抛出NullReferenceException。

引用Framework Design Guidelines 2nd ed.

如果成员不能成功执行其设计的操作,则应将其视为执行失败,并应抛出异常。

它没有做到它的名字所暗示的。抛出异常吧!

Keith的代码很好;记录异常并重新抛出是可以的。

这是一个私有方法,因此可以说设计准则不适用。但我仍然会在这里应用它们-为什么要用“if(result == null)”来弄乱您的代码,而不是使用异常处理呢?


有时候,拥有一个尝试创建对象但如果无法创建则返回null的方法是很有用的;然而,最好给这个方法起一个暗示它会这样做的名字。微软更喜欢这样的例程返回一个成功/失败的布尔值,并将一个对象引用变量作为byref参数来存储新对象,但我更喜欢返回值或null,因为它允许在尝试创建实例的语句中定义一个新变量。 - supercat
@supercat:如果我们能够相信其他开发人员(以及自己)始终检查返回值是否为null,那就没问题了... - TrueWill
如果调用一个名为TryGetFoo的函数而不考虑它可能失败,那么他们就会得到他们应得的结果。他们甚至更容易忽略来自MS模式“TryGetFoo”方法的返回值(成功/失败标志)。在某些情况下,MS模式版本确实具有一些功能,这可能是好事或坏事:一个函数可以抛出异常,但仍然在这样做之前将传入的变量设置为某些值。如果变量是IDisposable,则这可能是好的,但如果忽略了创建方法的返回代码,则可能会引起混乱。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接