【问题标题】:Is it OK to swallow all exceptions except the critical ones in certain scenarios?在某些情况下是否可以吞下除关键异常之外的所有异常?
【发布时间】:2013-11-23 02:17:18
【问题描述】:

在某些情况下,我只想调用某个方法来完成某些工作,而不关心处理它可能抛出的所有特定异常。相反,我真正关心的是方法是否成功。

我将提供一个 .NET / C# 示例。假设我有一个要复制的文件,我真正关心的是复制操作是否成功。如果复制失败,我不在乎特定异常是 FileNotFoundException 还是 IOException“磁盘空间不足”异常或其他什么...在这种情况下,我的应用程序将正常运行,因为此操作并不重要。

所以实现这个的想法是:

try 
{
  // try 
  System.IO.File.Copy(strFile, strFile + ".new");
} 
catch (Exception ex) 
{
  // if critical exception then rethrow
  if (IsCritical(ex)) 
      throw;

  // else just log and swallow...
  Console.WriteLine("Failed to copy the file: " + ex.Message);
}

其中 IsCritical(Exception ex) 是定义为的辅助方法:

public static bool IsCritical(Exception ex) 
{
  if (ex is OutOfMemoryException) return true;
  if (ex is AppDomainUnloadedException) return true;
  if (ex is BadImageFormatException) return true;
  if (ex is CannotUnloadAppDomainException) return true;
  if (ex is ExecutionEngineException) return true;
  if (ex is InvalidProgramException) return true;
  if (ex is System.Threading.ThreadAbortException) 
      return true;
  return false;
}

此问题基于以下文章:Exception Handling in C# with the "Do Not Catch Exceptions That You Cannot Handle" rule in mind

这个想法是遵循异常处理最佳实践的主要规则: - 不要在不重新抛出的情况下捕获一般异常 - 只捕获你知道如何处理的异常 - (在这种情况下,我想以相同的方式处理它们......通过记录并继续使用应用程序逻辑)。

那么对于给定的场景,这是一个好方法吗?如果不是,为什么以及做什么会更好?

【问题讨论】:

  • 就我个人而言,我会通过调用一个体面的日志框架(log4net、NLog、你的名字)来替换Console.WriteLine("Failed to copy the file: " + ex.Message);,以确保记录更多信息像堆栈跟踪等。
  • 问题是,您已经列举了两组异常——您认为很关键的异常和您确定是良性的(当前为 FileNotFoundException 和 IOException)。但肯定有 很多 例外当前不在您的列表中。您确定它们都属于良性类别吗?如果不是,那么他们肯定应该(在没有任何其他知识的情况下)与关键的一样对待吗?
  • 不是 .Net 的人,但在 Java 中这样做的方式是它们在异常和错误(都扩展 Throwable )之间有所不同,其中错误是例如 OutOfMemoryError 和异常都是应用程序级别的错误;即IOException。这就是为什么他们说永远不要捕获 Throwable (除非你真的知道你在做什么),这基本上就是参考文章所说的。

标签: c# .net exception exception-handling


【解决方案1】:

通常建议不要吞下异常的原因是它可以隐藏错误。例如,您正在做的不是File.Copy:您也在做字符串处理(strFile + ".new")。这不会抛出(OOM 除外),但如果计算更复杂,您可能隐藏了一个错误

在这种情况下,您可能应该将所有计算移出 try 块。然后可以吞下任何异常。我有记录它们的习惯,以防万一我很小心仍然犯了错误。

不要不必要地吞咽的规则是为了保护开发人员免于犯错误。如果你有理由确定一切都很好,那么你就不需要遵守规则。

【讨论】:

    【解决方案2】:

    这条线让我有点担心......

    如果复制失败,我不在乎特定异常是 FileNotFoundException 还是 IOException“磁盘空间不足”异常或其他异常

    与其说是 FNF 异常,不如说是“磁盘空间不足”等 - 这些是您可能不想忽略的异常。原因是,如果没有足够的磁盘空间,理论上,您的应用程序最终会失败。这实际上是您不应捕获一般异常的主要原因之一,因为您有效地掩盖了此类更大的问题。

    在更一般的说明中,为了更具体地回答您的问题,捕获一个更一般的异常是完全可以的,您确信它不会对您的应用产生任何重大影响,也不会像前面提到的那样(我重新-iterate 有充分的理由),不会掩盖任何更大/更严重的问题。

    【讨论】:

    • 好点,另一方面,处理所有可能的故障在实践中很少进行,因为努力是不合理的。我猜很少有程序可以处理磁盘已满的情况(这是正确的)。
    • @usr 是的,我绝不是说你应该每次都处理这些问题。然而,我担心的是 OP 似乎对这些例外被忽视的事实有多“松懈”。
    【解决方案3】:

    在特定情况下可以吞下特定异常,但实际上这取决于用例。

    我建议处理异常,您可以处理和使用AppDomain.UnhandledException 事件处理未处理的异常,并告知用户发生了什么。

    从调试的角度来看,这并不重要,只要您可以访问代码,因为您可以在 Visual Studio 中启用中断所有常见的运行时异常。 (调试 -> 异常 -> 公共语言运行时异常 -> 勾选左侧复选框)

    我永远不会依赖关键异常列表,因为您并不真正知道列表是否完整。

    【讨论】:

    • 最后的说法非常正确。最好考虑一些非关键的异常(可以被吞下)和所有其他异常(提到的那些+“意外”)不应该被吞下。
    【解决方案4】:

    我想你回答了你自己的问题。这完全取决于您的业务逻辑。但一般来说,如果我会“吞下”异常,但只能通过它们的特定类型,同样,你只是以另一种方式。

    【讨论】:

      【解决方案5】:

      您可以为同一次尝试添加多个 catch 语句:

      try
      {
       //code here
      }
       catch (FileNotFoundException ex1) 
      {
        //Do whatever you want
      }
      catch (IOException ex2)
      {
        //Do whatever you want
      }
      

      我建议处理您认为不重要的异常以及其余所有异常 只做一个catch (Exception er){throw ;}

      也仅仅针对这两个特定的异常就足以捕获IOException,因为IOExceptionFileNotFountException 的父类

      【讨论】:

      • 我看不出这是如何回答这个问题的。另外:catch (Exception er){throw er;} 对我来说毫无意义。
      • throw er; 不应该用于重新抛出异常,它会破坏异常中的先前调用堆栈并重新启动它。你应该改用throw;
      • 是的,你是对的,他可以避免 catch(Exception er) 只是为了给他这个想法
      • @ChrisMantle catch (Exception er){throw ;} 对我来说同样有意义:没有。这与完全省略整行相同。
      • @UweKeim 这更像是一般性评论。你是对的,不做任何其他事情就抓住并重新抛出是没有意义的。
      【解决方案6】:

      我倾向于说,任何来自不是您自己编写的代码的异常应该被视为严重异常,因为这意味着引发错误的系统很可能是处于未定义状态,因此您在该系统上尝试的任何后续操作都不能保证成功。

      因此我可能会做类似的事情:

        try 
          {
            System.IO.File.Copy(strFile, strFile + ".new");
           //MyLibrary throws exception clases I defined so I know what they are all about
            MyLibrary.SomeClass.DoSomething(strFile);
          }
          catch(ExceptionTypeIDefinedInMyLibraryWhichIKnowICanSafelyIgnore iex)
          {
            Console.WriteLine("Not to worry: "+ iex.Message);
          }
          catch (Exception ex) 
          {
            //This absolute is not something I can handle. Log it and throw.
            Log(ex); throw;
          }
      

      这样您就不能忽略严重异常,并且您只能从自己的子系统中抛出特定类型的异常,因为您可以控制它们抛出的内容。

      但是,如果您真的想遵循您展示的模式,我倾向于扭转这种情况,只处理您知道您可以处理的异常,并抛出其他所有内容。

      这样当一个新的OhMyGodThereAreZombiesInTheServer 异常被添加到 BCL 时,你就不会被烧毁,你永远不知道,它可能会发生......

      try 
      {
        // try 
        System.IO.File.Copy(strFile, strFile + ".new");
      } 
      catch (Exception ex) 
      {
        // if critical exception then rethrow
        if (ICanHandleThis(ex)) 
        {
         Console.WriteLine("Failed to copy the file: " + ex.Message);
        }
        else
        {
         //step aside and let Dr McNinja deal with it
         throw;
        }     
      }
      
      public static bool ICanHandleThis(Exception ex) 
      {
       //I don't care about these exceptions, they aren't critical
       //to my application for some reason.
       return (ex is SomeTrivialExceptionTypeIDontCareAbout 
              || ex is SomeOtherIgnorableExceptionType);
      
      }
      

      【讨论】:

        【解决方案7】:

        如果您希望您的程序不可调试并且管理员讨厌您,则可以吞下异常。

        您至少需要在较低的日志级别记录异常 debugtracefine 或其他。

        这将有助于调试问题、了解程序的工作原理或未发生某些事情的原因。

        唯一可以吞下的异常是那些绝对不会对应用程序产生影响的异常,这些异常非常罕见(例如睡眠中断异常)。看到这个nice article

        现在在您的示例中,您并没有完全吞下异常,而是在丢弃堆栈跟踪时打印消息。

        只需确保异常消息包含有关复制源和目标的信息,以便用户可以调试。同样取决于您的应用程序,了解应用程序代码的哪一部分正在执行复制可能会很有用。有时可能需要它。

        【讨论】:

          猜你喜欢
          • 2014-02-24
          • 2012-11-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多