33

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

他指出,良好实现的断言在某种程度上存储了错误条件的状态,例如:

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

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

所以,我认为我将 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");

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

4

8 回答 8

50

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

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

希望这可以帮助!

于 2008-09-14T09:26:56.133 回答
17

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

假设我们有一个带有 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 为空,则调用者错误地使用 ToString 并且应该在调用之前检查它。

两者的实现

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

     return Name;
}

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

于 2008-09-14T10:57:27.680 回答
7

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

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

我得出的良好经验法则:

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

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

  3. 如果一个断言发生故障(在单元测试中或在测试期间),则该类被窃听。

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

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

于 2010-01-09T16:06:10.720 回答
4

我使用显式检查,在公共受保护方法上抛出异常,在私有方法上使用断言。

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

于 2008-09-14T09:55:08.080 回答
3

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

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

此外,当异常未处理时,更好的做法是进行小型转储。VB 比 C# 更强大的一个领域是,您可以使用异常过滤器在异常发生时捕捉小型转储,而保持其余异常处理不变。 Gregg Miskelly 关于异常过滤器注入的博客文章提供了一种从 c# 执行此操作的有用方法。

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

于 2008-09-14T21:59:11.350 回答
2

IMO 这只是开发时间的损失。正确实施的异常可以让您清楚地了解发生了什么。我看到太多应用程序显示模糊的“断言失败:i < 10”错误。我认为断言是一种临时解决方案。在我看来,任何断言都不应该出现在程序的最终版本中。在我的实践中,我使用断言进行快速和肮脏的检查。代码的最终版本应考虑到错误情况并做出相应的行为。如果发生不好的事情,你有两个选择:处理它或离开它。如果传入错误的参数,函数应该抛出一个具有有意义描述的异常。我认为验证逻辑的重复没有任何意义。

于 2008-09-14T09:25:40.647 回答
1

良好使用 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

于 2011-05-11T12:31:25.817 回答
0

这是2美分。

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

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

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

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

于 2010-01-09T15:22:56.597 回答