2008-09-14 122 views
33

我刚刚开始浏览John Robbins的“调试MS .Net 2.0应用程序”,并且因为Debug.Assert(...)的传福音而感到困惑。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版本中)...或不。我错过了什么?

回答

48

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

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

希望这会有所帮助!

+10

断言可以用于*内部*方法调用的参数检查的方法(由属于同一个组件的代码调用),而不是外部的方法调用(由另一个组件调用) 。例如,我可能会断言Double类型的私有方法参数不是NaN。 – RoadWarrior 2008-10-31 11:48:52

2

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

4

我使用明确的检查,抛出异常公众保护私人方法的方法和断言。

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

17

有一个沟通方面来断言vs异常抛出。

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

的ToString如果实施这样的:

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

它说,名称应该永远不能为null并且在用户类的错误,如果它是。

如果是的ToString实现这样的:

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

    return Name; 
} 

它说,来电者被错误地使用的ToString如果名称为空,应检查之前调用。

实现既

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

    return Name; 
} 

说,如果名称为null,那里的用户类的错误,但我们仍要处理。 (用户在打电话前不需要检查姓名。)我认为这是Robbins推荐的安全类型。

3

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

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

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

关于资产的另一个注意事项......它们与单元测试代码中的错误条件的效果很差。值得拥有一个封装来关闭你的单元测试的断言。

0

这里是2美分。

我认为最好的方法是使用断言和例外。这两种方法之间的主要区别是,如果Assert语句可以很容易地从应用程序文本(定义,条件属性...)中移除,而抛出的Exception依赖于(最终)由一个更难删除的条件代码具有预处理器条件的多段)。

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

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

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

4

我在考虑关于测试问题时提供关于调试与断言的指导时,我已经考虑过这么长时间了。

你应该能够测试你的班级有错误的输入,坏的状态,无效的操作顺序和任何其他可能的错误情况,并断言应该从来没有旅行。每个断言都检查应该总是是真实的,不管执行的输入或计算。

经验好规则,我在抵达:

  1. 断言是不可靠的代码,其功能正常独立配置的更换。它们是互补的。

  2. 在单元测试运行期间,即使在喂入无效值或测试错误条件时,断言绝不应该跳闸。代码应该处理这些条件而不会发生断言。

  3. 如果断言行为(无论是在单元测试还是在测试过程中),该类都会被窃听。

对于其他所有错误 - 通常到环境(网络连接丢失)或滥用(调用者传递一个空值) - 这是更漂亮和更可以理解为使用硬检查&例外。如果发生异常,主叫方知道这可能是他们的错。如果发生断言,调用者知道它可能是代码中声明所在的错误。

关于重复:我同意。我不明白你为什么要用Debug.Assert和异常检查复制验证。它不仅给代码添加了一些噪音,而且对于谁在过错中混淆了水域,但它是一种重复的形式。一个良好的使用断言的

1

例子:

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 

我个人认为,断言应该,当你知道的东西是外面希望限制使用,但可以肯定它的合理安全继续。在所有其他情况下(我没有想到,请随时指出情况)使用例外来强制和快速地失败。

对于我来说,最重要的权衡是您是否希望使用异常来降低实时/生产系统以避免腐败并简化故障排除,或者您是否遇到了永远不应该被允许在测试/调试版本,但可以允许继续生产(记录警告当然)。

cf.http://c2.com/cgi/wiki?FailFast 复制并从Java问题改性:Exception Vs Assertion