【问题标题】:Debug.Assert vs. Specific Thrown ExceptionsDebug.Assert 与特定抛出的异常
【发布时间】:2008-09-14 09:19:44
【问题描述】:

我刚刚开始浏览 John Robbins 的“调试 MS .Net 2.0 应用程序”,并且对他对 Debug.Assert(...) 的宣传感到困惑。

他指出,良好实现的 Assert 存储了某种错误条件的状态,例如:

Debug.Assert(i > 3, "i > 3", "This means I got a bad parameter");

现在,就我个人而言,他如此喜欢在没有实际明智的“业务逻辑”评论的情况下重述他的测试,这对我来说似乎很疯狂,也许“由于 lobittyjam widgitification 过程,i

所以,我认为我将 Asserts 作为一种低级的“让我们保护我的假设”之类的东西......假设人们觉得这是一个只需要在调试中进行的测试 - 即你正在保护自己反对同事和未来的程序员,并希望他们实际测试一些东西。

但我不明白的是,他接着说除了正常的错误处理之外,您还应该使用断言;现在我的设想是这样的:

Debug.Assert(i > 3, "i must be greater than 3 because of the flibbity widgit status");
if (i <= 3)
{
    throw new ArgumentOutOfRangeException("i", "i must be > 3 because... i=" + i.ToString());
}

通过 Debug.Assert 重复错误条件测试,我得到了什么?如果我们谈论对一个非常重要的计算进行仅调试的双重检查,我想我会明白的......

double interestAmount = loan.GetInterest();
Debug.Assert(debugInterestDoubleCheck(loan) == interestAmount, "Mismatch on interest calc");

...但是对于肯定值得检查(在 DEBUG 和 Release 版本中)的参数测试,我没有得到它...或者不是。我错过了什么?

【问题讨论】:

    标签: c# exception-handling assert


    【解决方案1】:

    断言不用于参数检查。应始终进行参数检查(并准确地根据您的文档和/或规范中指定的前提条件),并在必要时抛出ArgumentOutOfRangeException

    断言用于测试“不可能”的情况,即您(在您的程序逻辑中)假设为真的事情。这些断言可以告诉您这些假设是否因任何原因而被打破。

    希望这会有所帮助!

    【讨论】:

    • 断言可用于内部方法调用(由属于同一组件的代码调用)方法的参数检查,而不是外部方法调用(由另一个组件调用) .例如,我可以断言 Double 类型的私有方法参数不是 NaN。
    • @Chris:您声明断言不用于参数检查。是否有一个原因?当我注入依赖对象时,我倾向于为参数检查抛出异常,尤其是在构造函数中。但是我被告知要使用断言。对于使用异常而不是断言,我没有合乎逻辑的解释。你能澄清吗?干杯
    • 别担心,我找到了原因。根据 Jon Skeet,stackoverflow.com/questions/1276308/exception-vs-assertion 的说法:“使用断言在您的代码中进行内部逻辑检查,并为您的直接代码控制之外的错误情况使用正常异常。”
    • 我在多年的开发过程中注意到,通常只有在数据过多时才能做出断言。 (例如状态存储的冗余)。根据定义,这不太好。更短更好的代码可能不允许有办法检查一致性。例如“如果我的 bool bIsReceiving 为真,那么 i32 iSourceIP 必须为非空”。这是一个无用布尔值的示例,您存储了两次相同的信息。只需创建一个返回 iSourceIP != null 的函数 IsReceiving()。我的 2 克拉。
    【解决方案2】:

    断言与异常抛出有一个通信方面。

    假设我们有一个带有 Name 属性和 ToString 方法的 User 类。

    如果 ToString 是这样实现的:

    public string ToString()
    {
         Debug.Assert(Name != null);
         return Name;
    }
    

    它说 Name 永远不应该为空,如果是的话,User 类中有一个错误。

    如果 ToString 是这样实现的:

    public string ToString()
    {
         if ( Name == null )
         {
              throw new InvalidOperationException("Name is null");
         }
    
         return Name;
    }
    

    如果 Name 为 null,则表示调用者使用 ToString 不正确,应在调用前检查。

    两者的实现

    public string ToString()
    {
         Debug.Assert(Name != null);
         if ( Name == null )
         {
              throw new InvalidOperationException("Name is null");
         }
    
         return Name;
    }
    

    表示如果 Name 为 null,则 User 类中存在错误,但无论如何我们都想处理它。 (用户在调用之前不需要检查姓名。)我认为这是 Robbins 推荐的安全类型。

    【讨论】:

      【解决方案3】:

      在针对测试问题提供有关调试与断言的指导时,我已经考虑了很长时间。

      您应该能够使用错误输入、错误状态、无效的操作顺序和任何其他可能的错误条件来测试您的类,并且断言应该永远出错。无论执行的输入或计算如何,每个断言都在检查是否应该始终为真。

      我得出的良好经验法则:

      1. 断言不能替代独立于配置正常运行的健壮代码。它们是互补的。

      2. 在单元测试运行期间不应触发断言,即使在输入无效值或测试错误条件时也是如此。代码应在不发生断言的情况下处理这些条件。

      3. 如果断言发生故障(在单元测试中或在测试期间),则该类出现错误。

      对于所有其他错误——通常是环境(网络连接丢失)或误用(调用者传递了一个空值)——使用硬检查和异常会更好也更容易理解。如果发生异常,调用者知道这很可能是他们的错。如果发生断言,调用者就知道这很可能是断言所在代码中的错误。

      关于重复:我同意。我不明白为什么要使用 Debug.Assert 和异常检查来复制验证。它不仅给代码增加了一些噪音,混淆了谁有过错,而且它是一种重复的形式。

      【讨论】:

        【解决方案4】:

        我使用显式检查,在 publicprotected 方法上抛出异常,在私有方法上使用断言。

        通常,显式检查可以防止私有方法看到不正确的值。所以真的,断言正在检查一个应该不可能的条件。如果断言确实触发,它会告诉我该类的一个公共例程中包含的验证逻辑存在缺陷。

        【讨论】:

          【解决方案5】:

          可以捕获并吞下异常,从而使错误对测试不可见。 Debug.Assert 不会发生这种情况。

          没有人应该拥有一个捕获所有异常的 catch 处理程序,但是人们无论如何都会这样做,而且有时这是不可避免的。如果您的代码是从 COM 调用的,则互操作层会捕获所有异常并将它们转换为 COM 错误代码,这意味着您不会看到未处理的异常。断言不会因此受到影响。

          此外,当异常未处理时,更好的做法是进行小型转储。 VB 比 C# 更强大的一个领域是,您可以使用异常过滤器在异常发生时捕捉小型转储,而保持其余异常处理不变。 Gregg Miskelly's blog post on exception filter inject 提供了一种从 c# 中执行此操作的有用方法。

          关于资产的另一个注意事项......它们与单元测试代码中的错误条件的交互很差。有一个包装器来关闭单元测试的断言是值得的。

          【讨论】:

            【解决方案6】:

            IMO 只是浪费了开发时间。正确实施的异常可以让您清楚地了解发生了什么。我看到 太多 应用程序显示模糊的“断言失败:i

            【讨论】:

              【解决方案7】:

              Assert 的好用例:

              Debug.Assert(flibbles.count() < 1000000, "too many flibbles"); // indicate something is awry
              log.warning("flibble count reached " + flibbles.count()); // log in production as early warning
              

              我个人认为应该在您知道某事超出理想限制的情况下使用 Assert,但您可以确定继续使用它是相当安全的。在所有其他情况下(请随意指出我没有想到的情况)使用异常来快速失败。

              对我而言,关键的权衡是您是否想要关闭带有异常的实时/生产系统以避免损坏并使故障排除更容易,或者您是否遇到了在测试/调试中绝不允许继续被忽视的情况版本,但可以允许继续生产(当然记录警告)。

              参见。 http://c2.com/cgi/wiki?FailFast 从java问题复制和修改:Exception Vs Assertion

              【讨论】:

                【解决方案8】:

                这里是 2 美分。

                我认为最好的方法是同时使用断言和异常。两种方法之间的主要区别,恕我直言,如果可以从应用程序文本中轻松删除 Assert 语句(定义,条件属性......),而抛出的异常(通常)依赖于更难删除的条件代码(带有预处理器条件的多段)。

                每个应用程序异常都应得到正确处理,而断言仅应在算法开发和测试期间满足。

                如果您将空对象引用作为例程参数传递,并使用此值,则会收到空指针异常。确实:为什么要写断言?在这种情况下,这是浪费时间。 但是类例程中使用的私有类成员呢?当这些值设置在某处时,如果设置了空值,最好用断言检查。那只是因为当您使用该成员时,您会得到一个空指针异常,但您不知道该值是如何设置的。这会导致程序在所有用于设置私有成员的入口点上中断。

                异常更有用,但它们可能(恕我直言)管理起来非常繁重,并且有可能使用太多异常。他们需要额外的检查,可能不希望优化代码。 就我个人而言,我只在代码需要深度 catch 控制(catch 语句在调用堆栈中非常低)或函数参数未在代码中硬编码时使用异常。

                【讨论】:

                  猜你喜欢
                  • 2010-11-30
                  • 1970-01-01
                  • 2011-05-30
                  • 1970-01-01
                  • 2011-04-28
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-06-16
                  • 2013-05-24
                  相关资源
                  最近更新 更多