【问题标题】:Which Error Handling Model Is More Robust?哪种错误处理模型更健壮?
【发布时间】:2009-09-13 18:39:47
【问题描述】:

我在这两种错误处理模型之间有点纠结:

  1. 为您的对象创建一个布尔值Error 和一个字符串ErrorMessage 属性。在对象的方法内部捕获所有异常,并使用调用者的条件逻辑传递消息,即:

    Dim o As New MyObject
    o.SomeMethod()
    If Not o.Error Then
        'Do stuff'
    Else
        Dim msg As String = o.ErrorMessage
        'do something with message'
    End If
    
  2. 在您的对象中抛出异常并在外部使用Try Catch 逻辑处理它们:

    Dim o As New MyObject
    Try
       o.SomeMethod()      
       'Do stuff'
    Catch ex As Exception
        Dim msg As String = ex.ErrorMessage
        'do something with message'
    End Try
    

对我来说,这两种方式的代码量似乎相同,只是您拥有ErrorErrorMessage 属性的属性代码。但是,您也可以判断何时发生错误,而无需检查异常。我应该选择哪种模式?

【问题讨论】:

    标签: vb.net error-handling


    【解决方案1】:

    我决定使用抛出异常而不是使用错误/返回代码。我最近才认真研究过这个。

    引发异常的第一个原因是您可能会忘记检查错误代码。如果您不检查它,那么您将在错误存在时继续工作。但是,对于异常,如果您忘记处理它们,那么异常将上升到顶部并停止所有处理。发生这种情况比在发生未知错误后继续要好。

    有关详细信息,请查看 Addison-Wesley 的 Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, Second Edition 中的异常章节。

    Joel Spolsky 实际上更喜欢错误/返回代码而不是异常,但很多人不同意他的观点。 Joel 支持返回码的帖子可以在 here 找到。查看this blog post 和所有与此主题相关的很好讨论的 cmets。

    【讨论】:

      【解决方案2】:

      更喜欢#2。详情请看这个excerpt on Exception Throwing来自微软开发的优秀Framework Design Guidelines,正如Dennis所说。请特别注意有关异常和性能的部分。

      短版:

      不返回错误代码。

      通过抛出异常来报告执行失败。

      不要将异常用于正常的控制流程。

      我强烈建议您阅读本书以进行完整的讨论,并附有一些 Microsoft 杰出人物的评论。

      【讨论】:

      • 仅供参考,这本书可在 Safari Books 上找到,是许多不同主题的优秀读物。
      • 如果您选择了框架设计指南(我强烈推荐它),请确保获得第二版。
      【解决方案3】:

      当发生异常时应该使用异常。

      例如当您期望一个对象时,您会收到一个空(无)对象。

      Bob 大叔在他的书 Clean code 中推荐了 Exceptions over Error code。

      他说

      这些 [错误代码] 方法的问题在于它们会使调用者混乱。调用者必须在调用后立即检查错误。不幸的是,它很容易忘记。因此,最好在遇到错误时抛出异常。调用代码更简洁。它的逻辑不会被错误处理所掩盖。

      【讨论】:

      • 但是您仍然不需要运行Try Catch 块吗?这与运行If/Else 语句相同,对吧?
      • 异常不是用于传达 exceptional 条件,而是用于传达 error 条件。这可能是一个微妙的区别,但它是一个重要的区别。一个应用程序的特殊之处可能对另一种应用程序来说并不特殊。实际上应该避免处理空引用异常,因为这些几乎总是表明代码中存在错误,应该在代码中进行修复,这样它们就不会发生。
      • @Jason:通常最终发生的是,您使用更多的 try/finally 块,而不是 try/catch 块。不要使用 catch 处理程序进行清理,因为它们不能保证运行。 finally 块无论如何都会运行,因此它们应该包含您的清理代码。您只想在可以实际做某事的时候处理异常;否则你应该让它在调用堆栈中冒泡。如果您必须在某个较低点记录异常,请务必仅使用 throw 关键字(而不是 throw ex)重新抛出它,以保留堆栈跟踪信息。
      【解决方案4】:

      我对第一个问题最大的问题是它是被动的,容易被忽视并且不是很标准化。程序员如何知道检查该属性?或者哪些属性/方法可以设置错误?或者哪个属性/方法访问导致设置错误?

      例如。在您的第一个示例代码中,如果 o.Error 为 True,则不清楚是对象的初始化还是对 SomeMethod 的调用导致设置了标志。

      异常模型是告诉您的用户发生错误的一种不可忽视的方式。如果没有明确的代码来处理这种情况,就无法避免。

      【讨论】:

      • 我已经开始实现第二个模型,并且意识到可以去除很多内部错误处理,因为可以在外部处理异常
      • 我认为历史对此提供了丰富的信息。 Win32 API 使用类似于第一种方法的技术,如GetLastError,只是它是一场噩梦,因为它不是很标准化。这就是为什么微软发明了一种叫做 .NET 的东西并决定在整个运行时进行结构化异常处理的部分原因,没有异常! :)
      【解决方案5】:

      它们都是公认的错误处理形式,但是 .NET 语言的首选是使用异常。

      使用返回码(数字或布尔值)存在一些问题,其中两个最大的问题是:

      • 很容易被程序员忽略/忽略。
      • 不能在所有情况下都使用。如果你的构造函数失败会发生什么?您无法从构造函数显式返回值。

      仅出于这些原因,您应该使用例外。异常提供了一种清晰、标准化的方式来指示任何故障,无论故障发生在何处。

      您也将最终获得更少的代码,因为您应该只在可以安全且适当地处理该异常的时间和地点捕获异常。

      【讨论】:

        【解决方案6】:

        我建议同时使用。

        为什么?

        “为工作使用正确的工具”

        返回码的“问题”是人们经常忘记处理它们。但是,异常并不能解决这个问题!人们仍然不处理异常(他们没有意识到需要处理某个异常,他们假设堆栈上的某个人会处理它,或者他们使用 catch() 并消除所有错误)。

        虽然未处理的返回码可能意味着代码处于不稳定状态,但未处理的异常通常保证程序会崩溃。这样更好吗?

        虽然在编写代码时可以轻松识别返回码,但通常无法确定您正在调用的方法可能会引发哪些异常(或者只是非常耗时)。这通常会导致很多非常糟糕的异常处理。

        异常应该用于“错误”。困难就在于此。如果尝试打开文件时找不到文件,那是“错误”还是“预期情况”?只有调用者知道。在任何地方使用异常基本上都会将每条状态信息提升为错误。

        归根结底,错误处理是程序员必须从事的工作。这个问题在返回码和异常中都存在。

        因此,我使用返回码来传递状态信息(包括“警告”),以及“严重错误”的异常。 (是的,有时很难判断某物属于哪个类别)

        .net 示例:

        Int32.Parse 抛出异常(即使它的异常都不是错误 - 由调用者验证结果并自行决定结果是否有效)。必须将每个调用都包含在 try/catch 中,这简直是一种痛苦(和性能打击)。如果您忘记使用 try/catch,一个简单的空白文本输入字段可能会使您的程序崩溃。

        因此,Int32.TryParse() 诞生了。这做同样的事情,但返回错误代码而不是异常,因此您可以简单地忽略错误(接受任何非法输入的默认值 0)。在许多现实生活中,这比 Int32.Parse() 更清洁、更快、更容易和更安全。

        “TryParse”使用命名约定让调用者清楚可能发生的错误,应该正确处理。另一种方法(强制程序员更好地处理错误)是将返回代码设置为 out 或 ref 参数,以便调用者明确意识到需要处理返回的错误。

        【讨论】:

        • 本书框架设计指南第 2 版。提供了所有这些方面的指导,包括 TryParse 模式以及何时使用它。 强烈推荐阅读。
        • 这种方法的一个主要问题是你有两种不同的风格,最终会出现很多不一致的地方。在您使用返回码的示例原因中,如果您希望在打开文件时存在文件但它不存在,是的,这是一个例外。即使你用File.Exists 测试保护它,文件打开仍然可能失败,因为文件在保护测试和打开之间被删除,在这种情况下它仍然是一个例外......
        • ...“在任何地方使用异常”将处理异常情况的责任提升到最合适的代码层,该层可以正确处理它。是的,Int32.Parse 会引发异常,因为无论您以哪种方式对其进行切片,尝试将非数字字符串数据转换为实际整数都是错误的。 Int32.TryParse 之所以“诞生”,是因为将字符串数据转换为数字数据是一种极其常见的 UI 模式,考虑到数量,异常的成本太高,并且使方法语义中定义明确的测试部分成为可能。而且Int32.TryParse还是可以扔的。
        • 您在这里缺少的是我的特定代码不关心文件是否存在 - 在我的上下文中它是 not 错误。通过预先判断这一点并强制将日常情况视为错误,您会在情况中引入大量额外的复杂性、更多的代码膨胀和更高程度的风险,但不一定会带来任何好处。 fopen() 方法是,如果文件可以打开,您将获得一个文件句柄,如果不能打开,您将获得一个 NULL - 这比未处理的异常。
        • TryParse 对真正的错误抛出异常,并将“验证困难”留给调用者的责任。这会导致更高效、更容易编写、更简单、更干净的代码,而且更安全。 (当然,在无效输入错误的情况下,没有什么可以阻止您调用 parse。这就是使用“正确的工作工具”而不是在您每次都盲目抛出异常时的美妙之处紧张)
        猜你喜欢
        • 2010-12-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-26
        • 1970-01-01
        • 1970-01-01
        • 2023-02-02
        • 2014-06-10
        相关资源
        最近更新 更多