【问题标题】:General Exception Handling Strategy for .NET.NET 的一般异常处理策略
【发布时间】:2009-06-26 17:32:15
【问题描述】:

我习惯于在每个方法中都有 try/catch 块。这样做的原因是我可以在违规点捕获每个异常并记录它。从我的阅读和与他人的交谈中,我明白这不是一个流行的观点。一个人应该只抓住准备处理的东西。但是,如果我没有抓住违规点,那么就有可能永远不会记录该违规行为并了解它。注意:当我接住但不处理时,我仍然会扔。这允许我让异常传播到可以处理它的东西,但仍然让我在违规点记录它。

那么...如何避免在每种方法中使用 try/catch,但仍然在错误发生时记录错误?

【问题讨论】:

  • 什么语言?不同的语言有不同的方式来处理从捕获点到抛出点的回溯。
  • 是什么促使您需要在违规点登录?如果您记录异常中包含的调用堆栈,您仍然可以在日志中查看发生了什么以及您的代码在该点结束时采取的路线
  • 在违规点记录有两个原因。一,该方法可以发送要记录的对象及其值。第二,调用堆栈中可能有更高的代码最终可能会忽略或错误处理异常并且不会被记录。

标签: c# .net exception-handling exception-logging


【解决方案1】:

不,不要抓住一切。异常在堆栈上向上传播。您所要做的就是确保异常在到达堆栈顶部之前被捕获。

这意味着,例如,您应该用 try/catch 块包围事件处理程序的代码。事件处理程序可能是“栈顶”。对于 ThreadStart 处理程序或来自异步方法的回调也是如此。

您还希望在层边界上捕获异常,但在这种情况下,您可能只想将异常包装在特定于层的异常中。

对于 ASP.NET,您可以决定允许 ASP.NET Health Monitoring 为您记录异常。

但您当然不需要在每个方法中都捕获异常。这是一个主要的反模式。我会大声反对您使用这种异常处理方式签入代码。

【讨论】:

  • +1 表示“不,不要抓住一切。”尽管我会说“不,请不要抓住所有问题”,但这是对此类问题的良好开端。 :) - 我发现代码在哪里执行此操作更难。
  • @JohnSaunders - 如果我们想记录导致异常的被调用函数中的一些参数怎么办?否则该信息将丢失
  • 这将是捕捉、记录、重新抛出的理由
  • @JohnSaunders - 在异常开始冒泡之前,编辑并继续是否可用于实际调试和修复代码?这就是我这样做的原因——生产力。
  • @JohnSaunders - 这是我想要 mixins 或多重继承或某种方式将所有垃圾排除在外的地方,除非我需要它,然后暂时放置 try-catch(all )-重新抛出所有内容(通过启动某种代码生成的设置)。反正我是这么想的。我目前正试图弄清楚我最近跳入的 vb.net 的异常处理。
【解决方案2】:

您可以在堆栈跟踪中看到所有内容 - 无需尝试/捕获每个方法。

坚持几条规则:

  1. 仅当您想使用自定义异常类型时才使用 try/catch
  2. 仅在上层需要知道的情况下才定义新的异常类型
  3. 在顶层尝试/捕获,而不是对每个方法都这样做

【讨论】:

    【解决方案3】:

    好的,在阅读了所有回答说您应该在顶层有一个 try/catch 之后,我将考虑另一种观点。

    我不会在 every 方法中放置 try/catch,远非如此。但是我在我预计会失败的代码部分(例如打开文件)以及我想向异常添加额外信息的地方使用 try/catch )。

    堆栈跟踪和“权限被拒绝”的消息可能足以让您作为程序员找出问题所在,但我的目标是为用户提供有意义的信息,例如“无法打开文件'C:\lockedfile.txt'。权限被拒绝。”。

    如:

    private void DoSomethingWithFile(string filename)
    {
        // Note: try/catch doesn't need to surround the whole method...
    
        if (File.Exists(filename))
        {
            try
            {
                // do something involving the file
            }
            catch (Exception ex)
            {
                throw new ApplicationException(string.Format("Cannot do something with file '{0}'.", filename), ex);
            }
        }
    }
    

    我还想提一下,即使是那些说“只有一次 try/catch”的人可能仍然会在他们的代码中使用 try/finally,因为这是保证正确清理等的唯一方法。

    【讨论】:

    • 仅供参考,ApplicationException 已弃用。我也不会捕获Exception,而只会捕获与我在try 块中所做的事情相关的那些异常,例如IOException
    • 一般来说,只在不捕获异常时才捕获异常会更糟。
    【解决方案4】:
    1. 捕获并重新抛出您无法处理的异常只不过是在浪费处理器时间。如果您无法对异常或关于异常做任何事情,请忽略它并让调用者响应它。
    2. 如果您想记录每个异常,全局异常处理程序就可以了。在 .NET 中,堆栈跟踪是一个对象;它的属性可以像其他任何东西一样被检查。您可以将堆栈跟踪的属性(甚至以字符串形式)写入您选择的日志。
    3. 如果要确保捕获每个异常,全局异常处理程序应该可以解决问题。事实上,任何应用程序都不应该没有。
    4. 您的 catch 块应该捕获您知道可以正常恢复的异常。也就是说,如果你能对它做点什么,抓住它。否则,让调用者担心它。如果没有调用者可以对此做任何事情,让全局异常处理程序捕获它并记录它。

    【讨论】:

    • 加 1 表示 #3 全局异常处理程序。
    【解决方案5】:

    我绝对不会对每个方法都使用 try catch 包装器(奇怪的是,我刚开始时就这样做了,但那是在我学会更好的方法之前)。

    1)为了防止程序崩溃和用户丢失他们的信息,我这样做

                    runProgram:
                    try
                    {
                        container.ShowDialog();
                    }
                    catch (Exception ex)
                    {
                        ExceptionManager.Publish(ex);
                        if (MessageBox.Show("A fatal error has occurred.  Please save work and restart program.  Would you like to try to continue?", "Fatal Error", MessageBoxButtons.YesNo) == DialogResult.Yes)
                            goto runProgram;
                        container.Close();
                    }
    

    容器是我的应用程序启动的地方,所以这基本上是在我的整个应用程序周围放置一个包装器,这样就不会导致无法恢复的崩溃。这是我不介意使用 goto 的极少数情况之一(它是少量代码并且仍然非常易读)

    2) 我只在我认为可能出错的方法中捕获异常(例如超时)。

    3) 就可读性而言,如果您有一个 try catch 块,其中在 try 部分中有一堆代码,在 catch 部分中有一堆代码,最好将该代码提取到一个命名良好的方法中。

       public void delete(Page page) 
       {
          try 
          {
             deletePageAndAllReferences(page)
          }
          catch (Exception e) 
          {
             logError(e);
          }
       }
    

    【讨论】:

      【解决方案6】:

      要在发生时执行此操作,您仍然需要尝试/捕获。但是您不一定需要在任何地方捕获异常。它们向上传播调用堆栈,当它们被捕获时,您将获得堆栈跟踪。因此,如果出现问题,您可以随时根据需要添加更多的 try/catch。

      考虑查看许多可用的日志记录框架之一。

      【讨论】:

      • 在发生点有 try/catch 的目的是确保它被记录下来。如果它传播,可能会发生其他异常和/或其他代码可能不会像我希望的那样最终记录它。
      • 考虑使用众多可用的日志框架之一
      • 他们可以提供有关执行此操作的最佳方法的见解。
      【解决方案7】:

      我认为您不需要在违规时捕获所有内容。您可以冒泡您的异常,然后使用 StackTrace 找出违规点实际发生的位置。

      另外,如果你需要一个 try catch 块,我听说的最好的方法是将它隔离在一个方法中,以免用巨大的 try catch 块使代码混乱。另外,在 try 语句中尽量少写语句。

      当然,重申一下,将异常冒泡到顶部并记录堆栈跟踪是一种比在整个代码中嵌套 try-catch-log-throw 块更好的方法。

      【讨论】:

        【解决方案8】:

        我会考虑使用ELMAH 进行异常处理,这几乎是“让异常发生”的概念。 ELMAH 将负责记录它们,您甚至可以将其设置为在某个项目的异常达到或超过特定阈值时向您发送电子邮件。在我的部门,我们尽可能远离 try/catch 块。如果应用程序出现问题,我们想立即知道问题是什么,以便我们修复它,而不是抑制异常并在代码中处理它。

        如果发生异常,则表示有问题。这个想法是让你的应用程序只做它应该做的事情。如果它正在做不同的事情并导致异常,您的响应应该是修复它发生的原因,而不是让它发生并在代码中处理它。这只是我/我们的理念,并不适合所有人。但是我们都被应用程序出于某种原因“吃掉”异常而被烧毁了太多次,没有人知道出了什么问题。

        而且永远永远不会捕获一般异常。始终,始终,始终捕获最具体的异常,以便如果抛出异常,但它不是您期望的类型,您会再次知道,因为应用程序将崩溃。如果您只是捕获(异常 e),那么无论抛出哪种类型的异常,您的 catch 块现在都将负责响应可能抛出的每种类型的异常。如果没有,那么你就会遇到整个“吃”的例外情况,出现问题但你永远不知道,直到可能为时已晚。

        【讨论】:

        【解决方案9】:

        如何避免在每种方法中使用 try/catch,但仍然在错误发生时记录错误?

        这取决于托管环境。 Asp.Net、WinForms 和 WPF 都有不同的方法来捕获未处理的异常。但是一旦全局处理程序传递了一个异常实例,您就可以确定异常的抛出点,因为每个异常都包含一个堆栈跟踪。

        【讨论】:

        • 根据反馈,我认为我现在对这种方法感到满意:我的 WCF 服务将仅在边界包含 try/catch(服务类本身,而不是 BLL 或 DAL 层)。在边界处,我将记录它并记录堆栈跟踪。然后我会抛出一个异常,以便客户端可以捕获并处理它。
        • 对于 WCF,请记住 Web 服务使用错误。如果要执行此操作,请选择一个 FaultContract,然后抛出 FaultException。您的顶级 try/catch 应该允许所有 FaultException 通过,因为另一层可能返回了特定错误。
        【解决方案10】:

        实际上,避免细粒度的尝试/捕获。允许异常在堆栈中向上遍历并被捕获到尽可能高的级别。如果您有特定的关注点,那么如果您担心异常级联,则将日志记录放在立即捕获中 - 尽管您仍然可以通过深入研究内部异常来解决这些问题。

        异常处理不应该是事后的想法。确保你始终如一地这样做。我见过很多人从每个方法的开头到结尾都放了一个广泛的 try/catch 并捕获一般异常。人们认为这有助于他们获得更多信息,但实际上并非如此。在某些情况下,多即是少,因为少即是多。我永远不会厌倦“应该使用异常来记录异常行为”的公理。如果可以,请恢复,并尝试减少总体异常的数量。当您尝试解决问题并在出现问题时看到数百个相同的 NullReferenceException 或类似异常时,没有什么比这更令人沮丧了。

        【讨论】:

          【解决方案11】:

          实现了异常,因此除非被抛出,否则它们没有成本。

          这对我来说意味着性能影响并不是一个强烈反对的论据。异常情况通常是……异常。

          【讨论】:

            猜你喜欢
            • 2011-01-23
            • 2016-02-14
            • 2011-07-08
            • 1970-01-01
            • 1970-01-01
            • 2021-06-22
            • 1970-01-01
            • 2010-12-10
            • 1970-01-01
            相关资源
            最近更新 更多