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

到目前为止,我已经做了一年的专业软件工程师,并获得了计算机科学学位。我知道c++和C中的断言有一段时间了,但直到最近才知道它们存在于c#和。net中。

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

我应该开始在我们的生产代码中使用断言吗?如果可以,什么时候使用它最合适?这样做会更有意义吗

Debug.Assert(val != null);

if ( val == null )
throw new exception();
68767 次浏览

Debug.Assert()放在代码中任何你想要进行完整性检查以确保不变量的地方。当编译Release版本时(即没有DEBUG编译器常量),对Debug.Assert()的调用将被删除,因此它们不会影响性能。

在调用Debug.Assert()之前,仍然应该抛出异常。断言只是确保在开发过程中一切都如预期的那样。

我不知道它在c#和。net中是怎样的,但在C中assert()只在使用- ddebug编译时工作-如果没有编译,最终用户将永远不会看到assert()。仅供开发人员使用。我经常使用它,它有时更容易跟踪错误。

我不会在产品代码中使用它们。抛出异常,捕获和记录。

在asp.net中也需要小心,因为断言可以显示在控制台上并冻结请求。

使用断言检查开发人员的假设,使用异常检查环境的假设。

如果您希望在生产代码中使用断言(即发布构建),您可以使用Trace。Assert而不是Debug.Assert。

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

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

您可以通过删除DefaultTraceListener来覆盖这种行为:查看Trace的文档。MSDN中的侦听器。

总之,

  • 使用调试。自由断言,以帮助在调试版本中捕获错误。

  • 如果你使用Trace。在用户界面模式下断言,您可能希望删除DefaultTraceListener以避免让用户感到不安。

  • 如果你测试的条件是你的应用程序无法处理的,你可能最好抛出一个异常,以确保执行不会继续。请注意,用户可以选择忽略断言。

您应该使用Debug。断言来测试程序中的逻辑错误。编译器只能通知您语法错误。因此,您肯定应该使用Assert语句来测试逻辑错误。比如测试一个销售汽车的项目,只有蓝色的宝马可以得到15%的折扣。编译器不能告诉你你的程序在执行这个操作时逻辑上是否正确,但是assert语句可以。

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

对于检查为空的示例,如果这是在仅限内部的API中,我可能会使用断言。如果它在一个公共API中,我肯定会使用显式检查和抛出。

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

当我不确定时,我通常更喜欢异常而不是断言。

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

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

或者避免重复条件检查

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

您应该始终使用第二种方法(抛出异常)。

同样,如果你是在生产环境中(并且有一个发布版本),抛出一个异常(在最坏的情况下让应用程序崩溃)比处理无效值和可能破坏客户数据(这可能要花费数千美元)要好。

代码完成

8防御性编程

8.2断言

断言是开发过程中使用的代码——通常是一个例程 或者允许程序在运行时检查自身的宏。当一个 如果断言为真,那就意味着一切都按照预期运行。 当它为false时,这意味着它在 代码。例如,如果系统假设一个客户信息 文件永远不会有超过50,000条记录,程序可能 包含记录数小于或等于的断言 到50000年。只要记录的数量小于或等于 5万,断言会沉默。如果它遇到超过 然而,它将大声地“断言”存在一个

.程序错误 断言在大型复杂程序中特别有用 在高可靠性程序中。它们使程序员能够更快地完成任务 排除不匹配的接口假设,以及在 代码被修改,依此类推 断言通常有两个参数:一个布尔表达式 描述应该是正确的假设和传递给

(…)

通常,您不希望用户看到断言消息 产品代码;断言主要用于开发期间 和维护。断言通常被编译到 开发时间和编译出来的代码用于生产。在 发展过程中,断言会产生矛盾的假设, 意外条件、传递给例程的坏值等等。 在生产过程中,将它们从代码中编译出来,以便

.断言不会降低系统性能
在我的书里从来没有。 在绝大多数情况下,如果你想检查一切是否正常,如果不正常就抛出

我不喜欢的是,它使调试构建在功能上与发布构建不同。如果调试断言失败,但功能在发布中工作,那么这有什么意义呢?如果断言者早已离开公司,没有人知道这部分代码,那就更好了。然后你就得花点时间去探索这个问题,看看它是不是真的是个问题。如果这是一个问题,那为什么那个人不第一时间扔呢?

对我来说,这建议使用调试。声称你把问题推给别人,自己解决问题。如果某件事应该是这样,但事实并非如此,那就扔掉。

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

John Robbins在调试Microsoft .NET 2.0应用程序中有一个关于断言的大章节。他的主要观点是:

  1. 断言。断言永远都不嫌多。
  2. 断言不替换异常。异常包括代码所需要的东西;断言涵盖了它所假设的事物。
  3. 编写良好的断言不仅可以告诉您发生了什么和在哪里(如异常),还可以告诉您原因。
  4. 异常消息通常是神秘的,需要您向后遍历代码以重新创建导致错误的上下文。断言可以保存错误发生时程序的状态。
  5. 断言还可以作为文档,告诉其他开发人员您的代码所依赖的隐含假设。
  6. 当断言失败时出现的对话框允许您将调试器附加到该流程,因此您可以在堆栈中四处查找,就像在那里放置了断点一样。

PS:如果你喜欢《代码完成》,我推荐你继续阅读这本书。我买这本书是为了学习如何使用WinDBG和转储文件,但前半部分包含了一些帮助避免bug的技巧。

根据IDesign标准,你应该

坚持每一个假设。平均每五行就有一个断言。

using System.Diagnostics;


object GetObject()
{...}


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

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

就其价值而言……我发现我的公共方法倾向于使用if () { throw; }模式来确保方法被正确调用。我的私有方法倾向于使用Debug.Assert()

我的想法是,对于我的私有方法,我是受控制的,所以如果我开始调用一个带有不正确参数的私有方法,那么我就在某个地方打破了我自己的假设——我不应该进入那种状态。在生产中,理想情况下,这些私有断言应该是不必要的工作,因为我应该保持内部状态有效和一致。与提供给公共方法的参数相比,任何人都可以在运行时调用公共方法:我仍然需要通过抛出异常来强制参数约束。

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

我想再添加四种情况,其中Debug。断言可以是正确的选择。

我还没有看到这里提到的断言可以在自动化测试期间提供额外的概念覆盖。举个简单的例子:

当作者认为他们已经扩展了代码的范围以处理额外的场景,并修改了一些高级调用者时,理想情况下(!)他们将编写单元测试来覆盖这种新情况。那么,完全集成的代码可能会表现得很好。

然而,实际上已经引入了一个微妙的缺陷,但在测试结果中没有发现。在这种情况下,被调用者变得不确定,只有发生提供预期的结果。或者,它产生了一个未被注意到的舍入误差。或者造成的错误在其他地方被抵消。或者不仅授予所请求的访问权限,还授予不应该授予的其他特权。等。

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

2)另外,有些测试编写起来很简单,但是考虑到最初的假设,成本很高而且没有必要。例如:

如果一个对象只能从某个安全的入口点访问,是否应该从每个对象方法对网络权限数据库进行额外查询,以确保调用者具有权限?当然不是。也许理想的解决方案包括缓存或其他一些功能扩展,但设计并不需要这样做。当对象被附加到不安全的入口点时,Debug.Assert()将立即显示。

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

假设它是一个嵌入式实时设备。在遇到格式不正确的数据包时抛出异常并重新启动会适得其反。相反,该设备可能受益于尽力而为操作,甚至在输出中呈现噪声。它也可能没有人机界面、日志记录设备,甚至在以发布模式部署时根本不能被人物理访问,最好通过评估相同的输出来提供错误意识。在这种情况下,自由断言和全面的预发布测试比异常更有价值。

4)最后,有些测试是不必要的,只是因为被调用方被认为非常可靠。在大多数情况下,代码的可重用性越高,在使其可靠方面投入的精力就越多。因此,对于来自调用方的意外参数,通常使用Exception,而对于来自被调用方的意外结果,则使用Assert。例如:

如果核心String.Find操作声明在搜索条件未找到时将返回-1,则可以安全地执行一个操作而不是三个操作。然而,如果它实际上返回-2,你可能没有合理的行动方针。用单独测试-1值来代替更简单的计算是没有帮助的,在大多数发布环境中,用测试来确保核心库按预期运行是不合理的。在这种情况下,assert是理想的。

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

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());
}

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

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

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

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

简而言之

Asserts用于保护和检查契约式设计约束,即确保你的代码、对象、变量和参数的状态运行在你的预期设计的within the boundaries and limits .中。

  • Asserts应该只用于调试和非生产版本。在发布版本中,编译器通常会忽略断言。
  • Asserts可以检查在你的系统控制下的错误/意外情况
  • Asserts不是用户输入或业务规则的一线验证机制
  • Asserts应该被用来检测意外的环境条件(不受代码控制),例如内存不足、网络故障、数据库故障等。虽然很少,但这些情况是可以预料到的(你的应用程序代码不能解决硬件故障或资源耗尽等问题)。通常,异常会被抛出——然后你的应用程序可以采取正确的操作(例如,重试数据库或网络操作,尝试释放缓存的内存),或者在异常无法处理时优雅地终止。
  • 一个失败的断言对你的系统应该是致命的——也就是说,不像异常,不要尝试捕获或处理失败的Asserts——你的代码在意想不到的领域运行。可以使用堆栈跟踪和崩溃转储来确定哪里出了问题。

断言有巨大的好处:

  • 帮助查找用户输入的缺失验证,或高级代码中的上游错误。
  • 代码库中的断言清楚地向读者传达了代码中所做的假设
  • Assert将在Debug构建的运行时进行检查。
  • 一旦对代码进行了详尽的测试,将代码重新构建为Release将消除验证假设的性能开销(但好处是,如果需要,后面的Debug构建将始终恢复检查)。

... 更详细地

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

什么时候应该使用Asserts?

  • 在系统、库API或服务的任何地方,函数的输入或类的状态假设是有效的(例如,当系统的表示层已经对用户输入进行了验证时,业务层和数据层类通常假设已经对输入进行了空检查、范围检查、字符串长度检查等)。
  • 常见的Assert检查包括无效假设会导致空对象解引用,零除数,数值或日期算术溢出,以及一般的带外/非设计行为(例如,如果使用32位int来模拟人类的年龄,Assert将谨慎,年龄实际上在0到125之间-100和10^10的值不是设计的)。
< p > .Net代码契约
在. net堆栈中,代码契约可以通过Debug.Assert使用除了…之外,或作为…的替代。代码契约可以进一步形式化状态检查,并可以帮助在编译时(或之后不久,如果在IDE中作为背景检查运行)检测违反假设的情况

契约式设计(DBC)检查包括:

  • Contract.Requires -收缩的先决条件
  • Contract.Ensures -收缩后置条件
  • Invariant -表示关于对象在其生命周期中所有点的状态的假设。
  • Contract.Assumes -在调用非契约装饰方法时安抚静态检查器。

我已经在这里阅读了答案,我认为我应该添加一个重要的区别。使用断言有两种非常不同的方式。一种是作为临时的开发人员快捷方式,表示“这不应该真的发生,所以如果它发生了,就告诉我,这样我就可以决定怎么做”,有点像一个条件断点,用于你的程序能够继续的情况。另一种方法是在代码中假设有效的程序状态。

在第一种情况下,断言甚至不需要出现在最终代码中。你应该在开发过程中使用Debug.Assert,如果/当不再需要时,你可以删除它们。如果你想要保留它们或者忘记删除它们,没有问题,因为它们在发布汇编中不会有任何后果。

但在第二种情况下,断言是代码的一部分。他们断言,你的假设是正确的,并记录了它们。在这种情况下,您确实希望将它们保留在代码中。如果程序处于无效状态,就不应该允许它继续运行。如果你不能承受性能上的损失,你就不会使用c#。一方面,如果发生这种情况,能够附加调试器可能是有用的。另一方面,您不希望堆栈跟踪弹出到您的用户,也许更重要的是您不希望他们能够忽略它。此外,如果是在服务中,它总是会被忽略。因此,在生产环境中,正确的行为是抛出一个Exception,并使用程序的正常异常处理,这可能会向用户显示一条漂亮的消息并记录详细信息。

Trace.Assert有完美的方法来实现这一点。它不会在生产环境中被删除,并且可以使用app.config配置不同的侦听器。 因此,对于开发来说,默认的处理程序就可以了,对于生产来说,您可以创建一个简单的TraceListener,如下所示,它会抛出一个异常,并在生产配置文件中激活它

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) { }
}

在产品配置文件中:

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

引用自实用主义程序员:从熟练工到大师

打开断言

有一个关于断言的普遍误解,由 编写编译器和语言环境的人。它会 就像这样:

断言给代码增加了一些开销。因为他们会检查东西 那不应该发生,它们只会被一个漏洞触发 代码。一旦代码经过测试并发布,它们就不再存在了 需要,应该关闭,以使代码运行得更快。 断言是一种调试工具 这里有两个明显错误的假设。首先,他们假设 测试可以找到所有的bug。实际上,对于任何复杂的程序 不可能测试哪怕是极小比例的排列 你的代码将会通过(参见无情测试) 第二,乐观主义者忘记了你的程序运行在一个 危险的世界。在测试过程中,老鼠可能不会咬断 通讯电缆,玩游戏的人不会耗尽内存,还有 日志文件不会填满硬盘驱动器。这些事情可能会在什么时候发生 您的程序在生产环境中运行。你的第一句话 防守方正在检查是否有任何可能的错误,你的第二个任务是使用

在将程序交付到生产时关闭断言是 就像在没有网的情况下穿过钢丝,因为你曾经做到过 在实践中。它有巨大的价值,但很难获得生命 保险。< / p > 即使你确实有性能问题,也只关闭那些 断言真的打击你.

.

..

.