当前位置:  开发笔记 > 编程语言 > 正文

我什么时候应该使用Debug.Assert()?

如何解决《我什么时候应该使用Debug.Assert()?》经验,为你挑选了13个好方法。

我已经是一名专业软件工程师,已经有一年的时间了,已经获得了CS学位.我已经知道C++和C中有一段时间的断言,但直到最近才知道它们在C#和.NET中存在.

我们的生产代码不包含任何断言,我的问题是......

我应该在生产代码中开始使用Asserts吗?如果是这样,它的使用何时最合适?这样做会更有意义吗?

Debug.Assert(val != null);

要么

if ( val == null )
    throw new exception();

Rory MacLeod.. 224

在调试Microsoft .NET 2.0应用程序中, John Robbins有一个关于断言的重要部分.他的要点是:

    自由地断言.你永远不会有太多的断言.

    断言不会取代异常.例外涵盖了您的代码所要求的内容; 断言涵盖了它所假设的事物.

    一个写得很好的断言不仅可以告诉你发生了什么以及在哪里(例如异常),而是为什么.

    异常消息通常很神秘,要求您通过代码向后工作以重新创建导致错误的上下文.断言可以在错误发生时保留程序的状态.

    断言加倍作为文档,告诉其他开发人员你的代码所依赖的隐含假设.

    断言失败时出现的对话框允许您将调试器附加到进程,因此您可以在堆栈周围查看,就像在那里放置断点一样.

PS:如果您喜欢Code Complete,我建议您继续阅读本书.我买它是为了学习使用WinDBG和转储文件,但上半部分包含了一些提示,以帮助避免错误.



1> Rory MacLeod..:

在调试Microsoft .NET 2.0应用程序中, John Robbins有一个关于断言的重要部分.他的要点是:

    自由地断言.你永远不会有太多的断言.

    断言不会取代异常.例外涵盖了您的代码所要求的内容; 断言涵盖了它所假设的事物.

    一个写得很好的断言不仅可以告诉你发生了什么以及在哪里(例如异常),而是为什么.

    异常消息通常很神秘,要求您通过代码向后工作以重新创建导致错误的上下文.断言可以在错误发生时保留程序的状态.

    断言加倍作为文档,告诉其他开发人员你的代码所依赖的隐含假设.

    断言失败时出现的对话框允许您将调试器附加到进程,因此您可以在堆栈周围查看,就像在那里放置断点一样.

PS:如果您喜欢Code Complete,我建议您继续阅读本书.我买它是为了学习使用WinDBG和转储文件,但上半部分包含了一些提示,以帮助避免错误.


+1简洁实用的摘要.非常直接适用.但是,对我来说,最重要的是使用Trace.Assert和Trace.Assert.也就是说,当你在生产代码中做/不想要它们时.
JonCoombs是"Trace.Assert vs. Trace.Assert"的错字?

2> Mark Cidade..:

Debug.Assert()所有代码放在您想要的代码中进行健全性检查以确保不变量.编译Release版本(即没有DEBUG编译器常量)时,Debug.Assert()将删除调用,因此它们不会影响性能.

你应该在调用之前抛出异常Debug.Assert().断言只是确保在你还在开发的时候,一切都如预期的那样.


如果你在调用它之前仍然抛出异常,你能否澄清为什么会置于断言中?还是我误解了你的答案?
@Oscar我认为这首先是使用断言的全部内容......好吧那么,你把异常放在它们面前 - 那么为什么要把断言放在后面呢?
@superjos:我不同意:MacLeod的回答中的第2点说明你确实需要断言和异常,但不是在同一个地方.在变量上抛出NullRefEx是没用的,然后在它上面执行Assert之后(断言方法在这种情况下永远不会显示对话框,这是断言的全部要点).MacLeod的意思是,在某些地方,你需要一个例外,在其他地方,Assert就足够了.
@romkyns您仍然必须包含它们,因为如果不这样做,当您在**Release**模式下构建项目时,所有验证/错误检查都将消失.

3> juan..:

从代码完成

8防御性编程

8.2断言

断言是在开发期间使用的代码 - 通常是例程或宏 - 允许程序在运行时检查自身.当断言为真时,这意味着一切都按预期运行.当它为假时,这意味着它已在代码中检测到意外错误.例如,如果系统假定客户信息文件永远不会有超过50,000条记录,则程序可能包含一条断言,即记录数量小于或等于50,000.只要记录数小于或等于50,000,断言就会保持沉默.但是,如果它遇到超过50,000条记录,它将大声"断言"程序中存在错误.

断言在大型复杂程序和高可靠性程序中特别有用.它们使程序员能够更快地清除不匹配的接口假设,修改代码时出现的错误,等等.

断言通常需要两个参数:一个布尔表达式,用于描述假定应该为true的假设,以及一个要显示的消息(如果不是).

(......)

通常,您不希望用户在生产代码中看到断言消息; 断言主要用于开发和维护期间.断言通常在开发时编译到代码中,并从代码中编译生产.在开发过程中,断言会清除相互矛盾的假设,意外情况,传递给例程的错误值等等.在生产过程中,它们是从代码中编译出来的,因此断言不会降低系统性能.


那么,如果生产中遇到的客户信息文件包含超过50,000条记录,会发生什么?如果断言是从生产代码中编译出来的,并且没有另外处理这种情况,那么这种拼法不会有麻烦吗?

4> Nicholas Pia..:

FWIW ...我发现我的公共方法倾向于使用if () { throw; }模式来确保正确调用该方法.我的私人方法倾向于使用Debug.Assert().

我的想法是,使用我的私有方法,我是一个受控制的人,所以如果我开始使用不正确的参数调用我自己的私有方法之一,那么我已经在某个地方打破了我自己的假设 - 我应该从未得到过进入那个州.在生产中,这些私有断言理想上应该是不必要的工作,因为我应该保持我的内部状态有效和一致.与给予公共方法的参数对比,任何人都可以在运行时调用这些参数:我仍然需要通过抛出异常来强制执行参数约束.

此外,如果某些内容在运行时不起作用(网络错误,数据访问错误,从第三方服务检索到的错误数据等),我的私有方法仍然可以抛出异常.我的断言只是为了确保我没有打破我自己关于对象状态的内部假设.


这是对良好实践的非常清晰的描述,它给出了一个非常合理的答案.

5> Justin R...:

使用断言来检查开发人员的假设和异常,以检查环境假设.



6> Mark Ingram..:

如果我是你,我会这样做:

Debug.Assert(val != null);
if ( val == null )
    throw new exception();

或者避免重复检查

if ( val == null )
{
    Debug.Assert(false,"breakpoint if val== null");
    throw new exception();
}


不,它没有 - 它在抛出异常之前的某个时刻进入代码.如果您在代码中的其他地方有try/catch,您可能甚至都没有注意到异常!
@MarkIngram -1给你答案,+1给你的评论证明它.对于某些特殊情况,这是一个很好的技巧,但对于所有验证来说,这似乎是一件奇怪的事情.
我想有些情况下你可能想要这样做,但你永远不应该抓到一般的例外!
这是如何解决问题的?有了这个,debug.assert变得毫无意义.
+1我遇到了很多问题,人们只是在没有做任何事情的情况下尝试/捕获异常,因此跟踪bug是一个问题

7> Joe..:

如果您希望在生产代码中使用Asserts(即Release版本),则可以使用Trace.Assert而不是Debug.Assert.

这当然会增加生产可执行文件的开销.

此外,如果您的应用程序在用户界面模式下运行,默认情况下将显示"断言"对话框,这可能会让您的用户感到有些不安.

您可以通过删除DefaultTraceListener来覆盖此行为:请查看MSDN中Trace.Listeners的文档.

综上所述,

使用Debug.Assert可以帮助捕获Debug构建中的错误.

如果在用户界面模式下使用Trace.Assert,则可能需要删除DefaultTraceListener以避免使用户感到不安.

如果您正在测试的条件是您的应用无法处理的情况,那么最好不要抛出异常,以确保执行不会继续.请注意,用户可以选择忽略断言.



8> user19113..:

断言用于捕获程序员(您的)错误,而不是用户错误.只有在用户无法触发断言时才应使用它们.例如,如果您正在编写API,则不应使用断言来检查API用户可以调用的任何方法中的参数是否为空.但是它可以在一个私有方法中使用,而不是作为API的一部分公开,以断言你的代码在它不应该传递时永远不会传递null参数.

当我不确定时,我通常偏爱断言而不是断言.



9> StuartLC..:

简而言之

Asserts 用于保护和检查设计合同约束,即:

Asserts应仅适用于Debug和非生产版本.在版本构建中,编译器通常会忽略断言.

Asserts 可以检查系统控制中的错误/意外情况

Asserts 不是用于用户输入或业务规则的第一行验证的机制

Asserts应该不会被用来检测突发环境条件(这是代码的控制之外),如超出内存,网络故障,数据库故障等.虽然罕见,这些条件是可以预期的(和你的应用程序代码无法修复像问题硬件故障或资源耗尽).通常,会抛出异常 - 您的应用程序可以采取纠正措施(例如,重试数据库或网络操作,尝试释放缓存的内存),或者如果无法处理异常,则优先中止.

失败的断言对您的系统来说应该是致命的 - 即与异常不同,不要尝试捕获或处理失败Asserts- 您的代码在意外的区域中运行.堆栈跟踪和故障转储可用于确定出错的地方.

断言有很多好处:

帮助查找缺少用户输入的验证,或更高级代码中的上游错误.

代码库中的断言清楚地将代码中的假设传达给读者

将在运行时在Debug构建中检查断言.

一旦代码已经被彻底测试,重建代码版本将删除验证假设的性能开销(但与后面的调试版本总是会恢复的检查,如果需要的利益).

... 更多详情

Debug.Assert表示在程序控制下由代码块的其余部分假定的关于状态的条件.这可以包括所提供参数的状态,类实例的成员状态,或者方法调用的返回处于其合同/设计范围内.通常,断言应该使线程/进程/程序崩溃所有必要的信息(堆栈跟踪,崩溃转储等),因为它们表明存在未设计的错误或未考虑的情况(即不要尝试捕获或处理断言失败),有一个可能的例外,当断言本身可能造成比bug更多的损害时(例如,当飞机进入潜艇时,空中交通管制员不会想要YSOD,尽管调试构建是否应该部署到生产 ...)

您应该何时使用Asserts? - 在系统,库API或服务中的任何位置,其中假定函数或类状态的输入有效(例如,在系统的表示层中已对用户输入进行验证时) ,业务和数据层类通常假设已经完成了对输入的空检查,范围检查,字符串长度检查等).- 常见Assert检查包括无效假设会导致空对象解除引用,零除数,数值或日期算术溢出,以及通用带外/非行为设计(例如,如果使用32位int来模拟人类年龄) ,谨慎的是Assert,年龄实际上在0到125左右 - -100和10 ^ 10的值不是为了设计的).

.Net代码合同
在.Net堆栈中,代码合同可以作为使用的补充或替代使用Debug.Assert.代码约定可以进一步形式化状态检查,并且可以在编译时(或之后不久,如果在IDE中作为后台检查运行)帮助检测违反假设的情况.

合同设计(DBC)检查包括:

Contract.Requires - 合同规定的前提条件

Contract.Ensures - 签约Postconditions

Invariant - 表达关于对象在其生命周期中的所有点的状态的假设.

Contract.Assumes - 在调用非合约修饰方法时,静默静态检查程序.



10> Quibblesome..:

绝大多数都不在我的书中.在绝大多数情况下,如果你想检查一切是否理智,那么扔掉,如果不是.

我不喜欢的是它使调试版本在功能上与发布版本不同.如果调试断言失败但功能在发布中有效,那么这有什么意义呢?当断言器长期离开公司并且没有人知道代码的那部分时,它会更好.然后,您必须花费一些时间来探索问题,看看它是否真的是一个问题.如果这是一个问题那么为什么不是第一个投掷的人呢?

对我来说,这表明使用Debug.Asserts你将问题推迟给别人,自己处理问题.如果事情应该是这样的话,那就不会抛出.

我想有可能是性能关键的场景,你想要优化你的断言,它们在那里很有用,但是我还没有遇到这样的场景.


你应该回答一些优点,因为你强调了一些经常引起关注的问题,他们中断了调试会话和误报的可能性.但是你缺少一些细微之处并正在编写"优化离开断言" - 这只能基于认为抛出异常并执行debug.assert是相同的.事实并非如此,它们具有不同的目的和特征,正如您在一些赞成的答案中所看到的那样.DW

11> devlord..:

根据IDesign标准,你应该

断言每一个假设.平均而言,每五行就是一个断言.

using System.Diagnostics;

object GetObject()
{...}

object someObject = GetObject();
Debug.Assert(someObject != null);

作为免责声明,我应该提到我没有发现实施这个IRL是否切合实际.但这是他们的标准.



12> Derek Park..:

仅在需要为发布版本删除检查的情况下使用断言.请记住,如果不在调试模式下编译,则不会触发断言.

给出check-for-null示例,如果这是在仅内部API中,我可能会使用断言.如果它在公共API中,我肯定会使用显式检查和抛出.



13> Jon Hanna..:

所有断言都应该是可以优化的代码:

Debug.Assert(true);

因为它正在检查你已经假设的东西是真的.例如:

public static void ConsumeEnumeration(this IEnumerable source)
{
  if(source != null)
    using(var en = source.GetEnumerator())
      RunThroughEnumerator(en);
}
public static T GetFirstAndConsume(this IEnumerable source)
{
  if(source == null)
    throw new ArgumentNullException("source");
  using(var en = source.GetEnumerator())
  {
    if(!en.MoveNext())
      throw new InvalidOperationException("Empty sequence");
    T ret = en.Current;
    RunThroughEnumerator(en);
    return ret;
  }
}
private static void RunThroughEnumerator(IEnumerator en)
{
  Debug.Assert(en != null);
  while(en.MoveNext());
}

在上文中,null参数有三种不同的方法.第一个接受它是允许的(它什么都不做).第二个抛出调用代码处理的异常(或不抛出,导致错误消息).第三个假设它不可能发生,并断言它是如此.

在第一种情况下,没有问题.

在第二种情况下,调用代码存在问题 - 它不应该GetFirstAndConsume使用null 调用,因此它会返回异常.

在第三种情况下,这个代码存在一个问题,因为en != null在它被调用之前它应该已经被检查过,所以它不是真的是一个错误.或者换句话说,它应该是理论上可以优化的代码Debug.Assert(true),sicne en != null应该永远是true!

推荐阅读
路人甲
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有