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

大约一年以来,我一直是一个专业的软件工程师,gradle于CS学位。 我已经知道关于C ++和C一段时间的断言,但直到最近才知道它们存在于C#和.NET中。

我们的产品代码没有任何断言,我的问题是这个…

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

Debug.Assert(val != null); 

要么

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

在debuggingMicrosoft .NET 2.0应用程序中 John Robbins有很多关于断言的部分。 他的要点是:

  1. 坚决主张。 你永远不能有太多的断言。
  2. 断言不会取代exception。 例外涵盖了你的代码需要的东西; 断言涵盖了它所承担的事情。
  3. 一个写得好的断言可以告诉你不仅仅是发生了什么事情,而且是在哪里(例外),但是为什么。
  4. exception消息通常可能是神秘的,要求您通过代码向后工作来重新创build导致错误的上下文。 断言可以在发生错误时保存程序的状态。
  5. 断言与文档一样,告诉其他开发者你的代码依赖于什么隐含的假设。
  6. 断言失败时出现的对话框允许您将debugging器附加到进程中,因此您可以像堆栈中放置断点那样围绕堆栈。

PS:如果你喜欢Code Complete,那么我build议你跟着这本书继续。 我买了它来学习使用WinDBG和转储文件,但前半部分提供的技巧,以帮助避免错误的第一位。

将代码中的Debug.Assert()放在需要进行健全性检查的位置,以确保不variables。 当你编译一个发布版本(即没有DEBUG编译器常量)时, Debug.Assert()的调用将被删除,所以它们不会影响性能。

在调用Debug.Assert()之前,您仍应该抛出exception。 断言只是确保一切都如预期,而你仍然在发展。

从代码完成

8防守编程

8.2断言

一个断言是在开发过程中使用的代码 – 通常是一个例程或macros – 允许程序在运行时检查自己。 当一个断言是真实的,这意味着一切都按预期运作。 当它是错误的,这意味着它已经检测到代码中的意外错误。 例如,如果系统假定客户信息文件永远不会超过50,000条logging,那么程序可能会包含断言logging数小于或等于50,000的断言。 只要logging数小于或等于5万,断言就会保持沉默。 如果遇到超过50,000条logging,则会大声“声明”程序中有错误。

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

一个断言通常需要两个参数:一个布尔expression式,描述假设是假的假设,如果不是假的,则显示一条消息。

(……)

通常情况下,您不希望用户在生产代码中看到断言消息; 断言主要用于开发和维护过程中。 断言通常在开发时编译到代码中,并编译成生产代码。 在发展过程中,断言消除了相互矛盾的假设,意外的情况,传递给日常事务的不良价值,等等。 在生产过程中,它们是从代码中编译的,所以断言不会降低系统性能。

使用断言来检查开发人员的假设和例外,以检查环境假设。

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

我的想法是,用我的私人方法,我是一个受控制的人,所以如果我开始用参数调用我自己的私人方法之一,那么我已经在某个地方打破了我自己的假设 – 我应该永远不会得到进入该状态。 在生产中,这些私人断言最好是不必要的工作,因为我应该保持我的内部状态的有效性和一致性。 与公共方法给出的参数相比较,可以在运行时由任何人调用:我仍然需要通过抛出exception来强制执行参数约束。

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

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

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

或者避免重复的条件检查

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

断言用于捕捉程序员(你)的错误,而不是用户错误。 只有在用户没有机会引发断言的情况下才能使用它们。 例如,如果您正在编写API,则不应使用断言来检查API用户可以调用的任何方法中的参数是否为空。 但是它可以用在一个私有方法中,而不是作为API的一部分暴露出来,以断言你的代码在不应该传递null参数的时候不会传递这个参数。

当我不确定的时候,我通常会赞成exception。

如果要在生产代码(即发布版本)中声明断言,则可以使用Trace.Assert而不是Debug.Assert。

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

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

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

综上所述,

  • 使用Debug.Assert自由地帮助捕捉debugging版本中的错误。

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

  • 如果您testing的条件是您的应用程序无法处理的情况,那么最好抛出一个exception,以确保执行不会继续。 请注意,用户可以select忽略断言。

大多数从来没有在我的书。 在绝大多数情况下,如果你想检查一切是否理智,那就抛出一些不正确的方法。

我不喜欢的事实是它使debugging版本在function上与发布版本不同。 如果一个debugging断言失败,但function在发布,那么这是什么意思? 当断言者早已离开公司,没有人知道这部分代码的时候,情况会更好。 那么你不得不杀了一些时间来探索这个问题,看看它是否真的有问题。 如果这是一个问题,那为什么不把这个人放在第一位呢?

对我来说,这是通过使用Debug.Assertsbuild议您将问题推迟给其他人,自己处理这个问题。 如果事情应该是这样的话,那就不是那么扔了。

我想有可能是性能的关键情况下,你想优化你的断言,他们在那里是有用的,但我还没有遇到这样的情况。

根据IDesign标准 ,你应该

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

 using System.Diagnostics; object GetObject() {...} object someObject = GetObject(); Debug.Assert(someObject != null); 

作为一个免责声明,我应该提一下,我没有发现实施这个IRL是可行的。 但这是他们的标准。

简而言之

  • Asserts检查系统控制中的错误/意外情况
  • Asserts不是validation用户input或业务规则的机制
  • Asserts不是用来检测意外的环境条件(在代码的控制之外),例如,内存不足,networking故障,数据库故障等。虽然很less,但这些条件是可以预料的(而且你的应用程序代码不能解决像硬件故障或资源耗尽)。
  • 不要试图捕捉或处理Asserts – 你的代码在意想不到的领域运行。 堆栈跟踪和崩溃转储可以用来确定出了什么问题。

断言有巨大的好处:

  • 声明清楚地向读者传达了代码中的假设
  • Debug版本中,将在运行时检查断言。
  • 一旦代码被彻底的testing,将代码重build为Release将会消除validation假设的性能开销(但是如果需要的话,以后的Debug版本总是会恢复检查的好处)。

… 更多详情

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

什么时候应该使用Asserts? – 在系统或库API或服务中的某个function或状态的input被假定为有效的任何时刻(例如,当系统的表示层中的用户input进行validation时,业务和数据层类通常假设已经完成了空检查,范围检查,string长度检查等操作)。 – 通用Assert检查包括无效假设会导致空对象解引用,零除数,数字或date算术溢出,以及一般带外/不是为行为devise的(例如,将人的年龄build模为32位整数,大多数types系统的粗粒度可能假设年龄实际上在0到125之间)。

.Net代码合同
在.NET堆栈中,除了使用Debug.Assert 之外 ,还可以使用Code Contracts 。 Code Contracts将这些假设正式化,并可以在编译时(或之后不久,如果在IDE中作为背景检查运行)检测违反假设的情况。

这扩展了运行时间之外的DBC风格检查的次数Assert

  • Contract.Requires – 合同前提条件
  • Contract.Ensures – 约定的后置条件
  • Invariant – 表示关于物体在其寿命的任何时刻的状态的假设。
  • Contract.Assumes – 在调用非契约装饰方法时,使静态检查器变得平静。

只有在您希望删除检查版本构build的情况下才使用断言。 请记住,如果不以debugging模式进行编译,则断言不会触发。

鉴于你的检查为空的例子,如果这是在一个只有内部的API,我可能会使用一个断言。 如果它在公共API中,我肯定会使用明确的检查和抛出。

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

 Debug.Assert(true); 

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

 public static void ConsumeEnumeration<T>(this IEnumerable<T> source) { if(source != null) using(var en = source.GetEnumerator()) RunThroughEnumerator(en); } public static T GetFirstAndConsume<T>(this IEnumerable<T> 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<T>(IEnumerator<T> en) { Debug.Assert(en != null); while(en.MoveNext()); } 

在上面,有三种不同的方法去空参数。 第一个接受它是允许的(它什么都不做)。 第二个抛出一个exception的调用代码来处理(或不会导致错误消息)。 第三个假设不可能发生,并断言是这样。

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

在第二种情况下,调用代码有问题 – 它不应该使用null调用GetFirstAndConsume ,所以它返回一个exception。

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

我想我会添加四个更多的情况下,Debug.Assert可以是正确的select。

1)我在这里没有提到的东西是额外的概念覆盖在自动化testing期间,断言可以提供 。 举一个简单的例子:

当一个更高级的调用者被一个认为已经扩展了代码范围的作者修改来处理额外的场景时,理想地(!)他们将编写unit testing来覆盖这个新的情况。 那么可能是完全集成的代码似乎正常工作。

但是,实际上已经引入了一个微妙的缺陷,但是在testing结果中没有发现。 被调用者在这种情况下变得不确定,只是提供了预期的结果。 或者它可能产生了一个被忽视的舍入错误。 或者引起了一个在其他地方同样被抵消的错误 或者不仅授予请求的访问权限,还授予不应该授予的额外权限。 等等。

此时,被调用者中包含的Debug.Assert()语句与由unit testing驱动的新情况(或边缘情况)结合在一起,可以在testing期间提供宝贵的通知,原始作者的假设已经失效,代码不应该没有额外的审查发布。 断言与unit testing是完美的合作伙伴。

2)另外, 一些testing写起来很简单,但是考虑到最初的假设,它是高成本和不必要的 。 例如:

如果一个对象只能从某个安全的入口点访问,那么应该从每个对象方法向networking权限数据库进行额外的查询,以确保调用者有权限? 当然不是。 也许理想的解决scheme包括caching或function的其他扩展,但devise并不需要它。 Debug.Assert()将立即显示对象何时被连接到不安全的入口点。

3)接下来,在某些情况下,您的产品在发布模式下进行部署时,可能对其全部或部分操作没有有用的诊断交互 。 例如:

假设它是一个embedded式实时设备。 抛出exception并在遇到错误的数据包时重新启动会适得其反。 相反,该设备可能从尽力而为的操作中受益,甚至可能会在其输出中产生噪声。 它也可能没有人机界面,日志logging设备,甚至在部署到发布模式时可以被人类物理访问,并且通过评估相同的输出来最好地提供错误意识。 在这种情况下,自由主张和彻底的预发布testing比例外更有价值。

4)最后, 一些testing是不必要的,因为被测者被认为是非常可靠的 。 在大多数情况下,可重用的代码越多,就越努力使其可靠。 因此,对于来自调用者的意外参数的exception是常见的,但是对来自被调用者的意外结果进行断言。 例如:

如果一个核心的String.Find操作声明,当search条件没有find的时候它会返回一个-1 ,你可以安全的执行一个操作而不是三个操作。 但是,如果实际返回-2 ,则可能没有合理的行动。 用一个单独testing-1值的代码replace简单的计算是无益的,而在大多数发行版环境中不合理地使用testing来代替代码,确保核心库按预期运行。 在这种情况下,断言是理想的。

引用语用程序员:从熟人到硕士

保持断言打开

对编写者和语言环境的人所发布的断言有一个普遍的误解。 它是这样的:

断言会为代码添加一些开销。 因为他们检查不应该发生的事情,他们只会被代码中的错误触发。 一旦代码已经过testing并发货,就不再需要它了,应该closures代码以使代码运行得更快。 断言是一个debugging工具。

这里有两个明显错误的假设。 首先,他们假设testing发现所有的错误。 实际上,对于任何复杂的程序,你都不可能testing你的代码将被置入的排列的极小的百分比(见无情testing)。

其次,乐观主义者忘记了你的程序运行在一个危险的世界。 在testing过程中,老鼠可能不会通过通讯线啃,有人玩游戏不会耗尽内存,日志文件也不会占满硬盘。 当您的程序在生产环境中运行时,可能会发生这些事情。 你的第一道防线是检查是否有任何可能的错误,第二道防线是使用断言来检测你错过的东西。

将程序投入生产时closures断言就像是在没有networking的情况下穿越高线,因为你曾经在实践中穿过它 。 有戏剧性的价值,但很难获得人寿保险。

即使你有性能问题,只closures那些真正打击你的断言

你应该总是使用第二种方法(抛出exception)。

另外,如果你正在生产(并且有一个发布版本),最好抛出一个exception(让应用程序在最坏的情况下崩溃)比使用无效值更好,也许会损坏你的客户的数据美元)。

您应该使用Debug.Assert来testing程序中的逻辑错误。 编译器只能通知你语法错误。 所以你应该定义使用Assert语句来testing逻辑错误。 就像testing销售汽车的计划一样,只有蓝色的宝马汽车才能享受15%的折扣。 编译器可以告诉你任何关于如果你的程序在逻辑上是正确的,但是断言声明可以。

我已经阅读了这里的答案,我想我应该添加一个重要的区别。 使用断言有两种截然不同的方式。 一个是作为一个临时开发人员的快捷方式,“这不应该真的发生,所以如果它让我知道,所以我可以决定做什么”,有点像条件断点,你的程序可以继续。 另一种方法是在代码中对有效的程序状态进行假设。

在第一种情况下,断言甚至不需要在最终的代码中。 您应该在开发过程中使用Debug.Assert ,并且可以在不再需要时删除它们。 如果你想离开他们,或者如果你忘记删除他们没有问题,因为他们将不会在释放编译任何结果。

但在第二种情况下,断言是代码的一部分。 他们呃,断言你的假设是真实的,并且logging下来。 在这种情况下,你真的想把它们留在代码中。 如果程序处于无效状态,则不应允许继续。 如果你负担不起性能打击你不会使用C#。 一方面,如果能够附加一个debugging器,可能会很有用。 另一方面,你不希望堆栈跟踪popup在你的用户,也许更重要的是你不希望他们能够忽略它。 此外,如果它在服务中,它将永远被忽略。 因此,在生产中,正确的行为是抛出exception,并使用正常的程序exception处理,这可能会向用户显示一个好消息并logging详细信息。

Trace.Assert有完美的方法来实现这一点。 它不会在生产中被移除,并且可以使用app.configconfiguration不同的监听器。 所以对于开发来说,默认处理程序是好的,对于生产,您可以创build一个简单的TraceListener,像下面这样引发exception,并在生产configuration文件中激活它。

 using System.Diagnostics; public class ExceptionTraceListener : DefaultTraceListener { [DebuggerStepThrough] public override void Fail(string message, string detailMessage) { throw new AssertException(message); } } public class AssertException : Exception { public AssertException(string message) : base(message) { } } 

And in the production config file:

 <system.diagnostics> <trace> <listeners> <remove name="Default"/> <add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/> </listeners> </trace> </system.diagnostics> 

I don't know how it is in C# and .NET, but in C will assert() only work if compiled with -DDEBUG – the enduser will never see an assert() if it's compiled without. It's for developer only. I use it really often, it's sometimes easier to track bugs.

I would not use them in production code. Throw exceptions, catch and log.

Also need to be careful in asp.net, as an assert can show up on the console and freeze the request(s).