【问题标题】:Catching base Exception class in .NET在 .NET 中捕获基异常类
【发布时间】:2008-09-22 12:48:47
【问题描述】:

我一直听到这个

catch (Exception ex)

这是一种不好的做法,但是,我经常在事件处理程序中使用它,例如,操作可能会进入网络,从而可能导致许多不同类型的故障。在这种情况下,我会捕获所有异常并在消息框中向用户显示错误消息。

这是否被认为是不好的做法?对于这个例外,我无能为力:我不希望它停止应用程序,用户需要知道发生了什么,而且我处于代码的顶层。我还应该做什么?

编辑:

人们说我应该查看调用堆栈并专门处理错误,因为例如无法有意义地处理 StackOverflow 异常。但是,停止该过程是最糟糕的结果,我想不惜一切代价阻止这种情况。如果我无法处理 StackOverflow,那就这样吧 - 结果不会比不捕获异常更糟糕,而且在 99% 的情况下,就我而言,通知用户是最不坏的选择。

此外,尽管我尽最大努力找出所有可能引发的异常,但在大型代码库中,我很可能会遗漏一些。而对于他们中的大多数人来说,最好的防御仍然是通知用户。

【问题讨论】:

    标签: c# exception


    【解决方案1】:

    不好的做法是

    catch (Exception ex){}
    

    和变种:

    catch (Exception ex){ return false; }
    

    等等

    在顶层捕获所有异常并将它们传递给用户(通过记录它们或在消息框中显示它们,具体取决于您是在编写服务器应用程序还是客户端应用程序),这正是做正确的事。

    【讨论】:

    • 如果您在 catch 语句中没有日志记录或任​​何内容,则您已经消除了以后跟踪错误的任何机会。如果您捕获 /specific/ 异常(例如,如果您尝试使用现有 Parse() 方法实现 TryParse() 方法,您将捕获 FormatException)返回 false 可能是正确的,但我看不到捕获异常时如何正确。
    【解决方案2】:

    我发现那些过于教条的论点总是不好的。他们和其他一切一样,都有一席之地。

    那个地方不是你的库代码,也不是你为你的应用定制开发的类。正如许多人所提到的,该位置是应用程序的最顶层,如果引发 any 异常,则很可能是出乎意料的。

    这是我的一般规则(和所有规则一样,它的设计目的是在适当的时候打破):

    我使用类和定制库来完成应用程序中的大部分工作。这是基本的应用程序架构——非常基本,请注意。这些人尝试处理尽可能多的异常,如果他们真的无法继续,则将可用的最具体的类型返回给 UI。

    在 UI 上,我倾向于总是从事件处理程序中捕获所有内容。如果对捕获特定异常有合理的期望,并且我可以对此做一些事情,然后我会捕获特定异常并优雅地处理它。但是,这必须在全部捕获之前,因为 .NET 只会使用与您的异常匹配的第一个异常处理程序。 (总是从最具体到最通用的顺序!)

    如果除了错误输出之外我对异常无能为力(例如,数据库处于脱机状态),或者如果异常确实是意外的,catch all 将接受它,记录它,并快速安全地进行故障保护,并使用死前向用户显示的一般错误消息。 (当然,有些类型的错误几乎总是会不正常地失败——OutOfMemory、StackOverflow 等。我很幸运,我还没有处理那些在 prod 级代码中的错误......到目前为止!)

    一网打尽。那个地方不是隐藏异常,那个地方不是尝试和恢复(因为如果你不知道你捕获了什么,你怎么可能恢复),那个地方不是为了防止错误显示给用户,同时允许您的应用会在未知和错误的状态下继续执行。

    Catch all 的地方是作为最后的手段,这是一个陷阱,以确保如果有任何东西通过您精心设计和严密防守的防御,至少它会被适当地记录下来,并且可以干净地退出。如果您在较低级别没有精心设计和保护良好的防御,这不好的做法,并且在较低级别是非常不好的做法,但已经完成作为最后的手段,(在我看来)不仅可以接受,而且通常是正确的做法。

    【讨论】:

      【解决方案3】:

      当我看到

      catch (Exception ex)
      

      我的手开始摸索一把锤子。几乎没有任何借口可以捕获基本异常。只有我想到的有效案例是:
      1) 3rd 方组件抛出异常(该死的作者)
      2) 非常顶级的异常处理(作为最后的手段)(例如在 WinForms 应用程序中处理“未处理”的异常)

      如果您发现可能发生许多不同类型异常的情况,则说明设计不佳。

      我不同意阿明·罗纳赫的观点。如果引发 StackOverflow 异常,你会怎么做?尝试执行额外的操作可能会导致更糟糕的后果。仅当您可以以有意义且安全的方式处理异常时才捕获异常。捕获 System.Exception 以覆盖 range 可能的异常是非常错误的。即使你重新扔它。

      【讨论】:

      • 但肯定比停止应用程序更好?我可能无法有意义地处理 StackOverflow,但可能发生的更糟糕的情况是什么?应用程序停止。但是我能够处理许多发生意外异常的情况。特别是。如果操作依赖于外部影响。
      • 如果你不知道如何处理异常,那就不要处理它。快速失败,防止应用程序的状态被破坏,了解失败的原因,并在下一个版本中正确处理这种情况。
      • Groky 只是尝试处理 StackOverflow 的 OutOfMemory 异常。然后告诉我们感觉如何:)
      • 实际上来自 CLR 的 stackoverflow 是 NOT 可捕获的;所以在这种情况下,程序将中止,就像它应该的那样。
      • 你创建一个线程,不要处理一些晦涩的 I/O 异常,然后看着整个过程失败而被遗忘。 catch (Exception e) 非常适用于绝对不会失败的强大应用程序。把你的锤子收起来。
      【解决方案4】:

      在代码的最高级别捕获异常是完全有意义的。只要您不需要根据异常类型执行任何不同的逻辑,就可以捕获基本异常类型。

      另外,请确保您显示的是友好的一般错误消息,而不是显示实际的异常消息。这可能会导致安全漏洞。

      【讨论】:

      • 如果你没有在异步回调中捕获异常,你认为会发生什么?
      【解决方案5】:

      是的,可以在应用程序的顶层捕获基本执行,这就是您正在做的事情。

      您得到的强烈反应可能是因为在任何其他级别,捕获 Base 异常几乎总是错误的。特别是在图书馆中,这将是非常糟糕的做法。

      【讨论】:

        【解决方案6】:

        从某种意义上说,你不应该在任何地方都这样做是不好的做法。

        在这种情况下,我认为这是唯一合理的解决方案,因为您的例外可能真的是任何事情。唯一可能的改进是在捕获所有特定错误情况之前添加额外的处理程序,您可以对异常执行一些操作。

        【讨论】:

          【解决方案7】:

          如果您重新引发无法正确处理的异常,那是完全可以的。如果您只是捕获异常,您可以隐藏代码中您不期望的错误。如果您捕获异常以显示它们(并绕过 die-and-print-traceback-to-stderr 行为),这是完全可以接受的。

          【讨论】:

            【解决方案8】:

            我认为张贴者指的是这样的异常处理:

            try {something} catch (SqlException) {do stuff} catch (Exception) {do other stuff}
            

            这里的想法是,您希望首先捕获更具体的错误(如 SqlException)并适当地处理它们,而不是总是依赖于包罗万象的一般异常。

            传统观点认为这是进行异常处理的正确方法(并且单独的 Catch (Exception ex) 是不好的)。在实践中,这种方法并不总是有效,尤其是当您使用其他人编写的组件和库时。

            根据组件在开发环境中的行为方式,这些组件通常会在生产环境中引发与代码预期的不同类型的异常,即使两种环境中的基本问题相同。这是 ASP.NET 中一个非常常见的问题,并且经常导致我使用裸 Catch (Exception ex) 块,它不关心抛出什么类型的异常。

            结构化异常处理在理论上是一个好主意。实际上,在您控制的代码域中,它仍然是一个好主意。一旦你引入了第三方的东西,它有时就不能很好地工作了。

            【讨论】:

              【解决方案9】:

              我们经常使用 Catch ex 作为异常(VB.Net 变体)。我们记录它,并定期检查我们的日志。追查原因并解决。

              我认为 Catch ex as Exception 是完全可以接受的一旦您正在处理生产代码,并且您有一种通用的方法来优雅地处理未知异常。就我个人而言,在我完成一个模块/新功能并对我在测试中发现的任何异常进行专门处理之前,我不会使用通用捕获。这似乎是两全其美。

              【讨论】:

                【解决方案10】:

                没有;在这种情况下,如果您不想停止程序,那么您无能为力,并且在顶层是正确的地方,只要您正确登录并且不希望将其隐藏起来咧嘴一笑

                【讨论】:

                  【解决方案11】:

                  重要的是要了解异常通过应用程序的路径,而不是随意抛出或捕获它们。例如,如果您捕获的异常是内存不足怎么办?您确定在这种情况下会显示您的对话框吗?但是定义一个最后的异常点并说你永远不希望错误传播到那个点之后肯定没问题。

                  【讨论】:

                    【解决方案12】:

                    您应该捕获与您正在做的事情相关的异常。如果您查看您调用的方法,您将看到它们抛出了哪些异常,并且您希望更具体地处理这些异常。您应该有权了解您调用的方法可能会引发哪些异常,并适当地处理它们。

                    而且...比一次大的尝试捕获更好,在需要捕获的地方尝试并捕获。

                    try {
                       myThing.DoStuff();
                    }
                    catch (StuffGoneWrongException ex) {
                       //figure out what you need to do or bail
                    }
                    

                    也许没有那么紧密,但这取决于你在做什么。请记住,这项工作不仅仅是编译它并将其放在某人的桌面上,您还想知道如果发生了什么问题以及如何修复它。 (在这里插入关于追踪的咆哮)

                    【讨论】:

                      【解决方案13】:

                      很多时候异常被捕获以释放资源,是否(重新)抛出异常并不重要。在这些情况下,您可以避免 try catch:

                      1) 对于一次性对象,您可以使用“using”关键字: 使用(SqlConnection conn = 新 SqlConnection(connStr)) { //代码 } 一旦您超出使用范围(通常或通过 return 语句或通过异常),Dispsose 方法会自动在对象上调用。换句话说,它就像 try/finally 构造。

                      2) 在asp.net中,你可以拦截Page对象的Error或UnLoad事件来释放你的资源。

                      希望能帮到你!

                      【讨论】:

                        【解决方案14】:

                        我正在回复“但是,停止进程是最糟糕的结果......”

                        如果您可以通过运行不同的代码(使用 try/catch 作为控制流)、重试、等待和重试、使用不同但等效的技术(即回退方法)重试来处理异常,那么请务必这样做。

                        进行错误消息替换和记录也很好,除非它是那种伪礼貌的被动攻击性“联系你的管理员”(当你知道没有管理员并且如果有管理员不能做任何事情时关于它!)但是在你这样做之后,应用程序应该结束,即与未处理的异常相同的行为。

                        另一方面,如果您打算通过将用户返回到可能破坏其状态的代码线程来处理异常,我会说这比结束应用程序并让用户重新开始。是让用户从头开始重启更好,还是让用户销毁数据更好?

                        如果我在决定我可以从哪些账户取款的模块中遇到意外异常,我真的要记录并报告异常并将用户返回到取款屏幕吗?据我们所知,我们刚刚授予他从所有账户提取资金的权利!

                        【讨论】:

                        • 我想这取决于你正在编写什么样的应用程序。在银行示例中很公平,但是如果用户正在编辑文档并且他的所有更改都将丢失,则可能是另一回事。
                        • 假设调用 OpenDocument 并抛出一些意外的异常。某些东西破坏了线程状态的相对可能性是多少,而一旦处理了部分打开的文档,线程就很好了?如果有一个有用的异常层次结构来区分状态受干扰的异常和操作未发生的异常,则可以避免捕获前者,同时捕获后者的意外变化。不幸的是,不存在这样的层次结构。
                        【解决方案15】:

                        这对于捕获可以处理的异常非常有用。但有时也会发生由于环境不稳定或用户只是正确执行该过程,应用程序会遇到意外异常。您尚未在代码中列出或处理。有没有办法从 app.config 文件中捕获未处理的异常并显示常见错误?

                        还将详细异常消息放入日志文件中。

                        【讨论】:

                          【解决方案16】:

                          我一直在处理异常情况,这是我目前正在遵循的实现结构:

                          1. 在 Try / Catch 之外将所有内容调暗为 Nothing / String.Empty / 0 等。
                          2. 将 Try / Catch 中的所有内容初始化为所需的值。
                          3. 首先捕获最具体的异常,例如FormatException,但作为最后的手段保留基本异常处理(你可以有多个 catch 块,记住)
                          4. 几乎总是抛出异常
                          5. 让 global.asax 中的 Application_Error sub 优雅地处理错误,例如调用自定义函数将错误的详细信息记录到文件中并重定向到某个错误页面
                          6. 杀死你在 finally 块中变暗的所有对象

                          我认为最近不“正确”处理异常是可以接受的一个例子是使用通过 HTTP GET 传递到页面的 GUID 字符串 (strGuid)。我本可以在调用 New Guid(strGuid) 之前实现了一个函数来检查 GUID 字符串的有效性,但这样做似乎相当合理:

                          Dim myGuid As Guid = Nothing
                          
                          Try
                              myGuid = New Guid(strGuid)
                              'Some processing here...
                          
                          Catch ex As FormatException
                              lblError.Text = "Invalid ID"
                          
                          Catch ex As Exception 
                              Throw
                          
                          Finally
                              If myGuid IsNot Nothing Then
                                  myGuid = Nothing
                              End If
                          
                          End Try
                          

                          【讨论】:

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