Debug.Assert与特定的抛出异常

时间:2008-09-14 09:19:44

标签: c# exception-handling assert

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

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

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

现在,就个人而言,我似乎很生气,他如此喜欢在没有真正明智的“商业逻辑”评论的情况下重申他的考试,或许“因为flobittyjam widgitification过程,我必须永远不会发生这种情况”。

所以,我认为我认为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版本中)......或者不是。我错过了什么?

8 个答案:

答案 0 :(得分:48)

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

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

希望这有帮助!

答案 1 :(得分:17)

断言与异常抛出有沟通方面。

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

如果像这样实现ToString:

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

它表示Name永远不会为null,如果是,则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类中就会出现bug,但我们还是要处理它。 (用户在打电话之前不需要检查姓名。)我认为这是罗宾斯推荐的那种安全措施。

答案 2 :(得分:5)

在提供关于测试问题的调试与断言的指导时,我已经考虑过这个漫长而艰难的事情。

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

我已经达到了良好的经验法则:

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

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

  3. 如果断言跳闸(无论是在单元测试中还是在测试期间),该类都会被窃听。

  4. 对于所有其他错误 - 通常是环境(网络连接丢失)或误用(调用者传递空值) - 使用硬检查和更好的方法更好,更容易理解。例外。如果发生异常,则调用者知道它可能是他们的错。如果发生了断言,则调用者知道它可能是断言所在代码中的错误。

    关于重复:我同意。我不明白为什么你会用Debug.Assert和异常检查来复制验证。它不仅会给代码增加一些噪音,而且还会让人感到困惑,因为这是一种重复的形式。

答案 3 :(得分:4)

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

通常,显式检查会保护私有方法无论如何都看不到错误的值。所以,断言正在检查一个应该是不可能的条件。如果一个断言触发,它告诉我在该类的一个公共例程中包含的验证逻辑中存在缺陷。

答案 4 :(得分:3)

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

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

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

关于资产的另一个注意事项......他们在单元测试代码中的错误条件时表现不佳。有一个包装器可以关闭单元测试的断言。

答案 5 :(得分:2)

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

答案 6 :(得分: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

答案 7 :(得分:0)

这是2美分。

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

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

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

异常更有用,但它们可以(imho)非常繁重地管理,并且有可能使用太多异常。并且它们需要额外的检查,可能不希望优化代码。 就个人而言,只有当代码需要深度catch控件(调用堆栈中的catch语句非常低)或者代码中没有硬编码函数参数时,我才会使用异常。