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


220

我已经获得CS学位,现在已经是专业软件工程师大约一年了。我已经在C ++和C中了解断言了一段时间了,但是直到最近才完全不知道它们存在于C#和.NET中。

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

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

Debug.Assert(val != null);

要么

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

2
您设置的二分法就是线索。它不是一个问题,也不是关于异常和断言的问题,还是关于防御性代码的问题。什么时候要做,这就是您想要了解的。
卡斯珀·莱昂·尼尔森

5
我曾经读过有人建议,异常或其他崩溃方法适用于“我无法明智地从中恢复”的情况,而断言适用于“永远不应该发生”的情况。但是,哪些现实情况在不满足前者条件的情况下满足了后者条件?来自断言保留在生产环境中的Python背景,我从未理解过在生产中关闭某些验证的Java / C#方法。我真正能看到的唯一情况是验证是否昂贵。
Mark Amery 2013年


2
就个人而言,我将异常用于公共方法,并将断言用于私有方法。
弗雷德(Fred)2016年

Answers:


230

调试Microsoft .NET 2.0应用程序中, John Robbins在断言中有很大一部分。他的主要观点是:

  1. 自由地断言。您永远不能有太多的断言。
  2. 断言不会替代异常。异常涵盖了代码所要求的内容;断言涵盖了它所假设的事物。
  3. 一个写得很好的断言不仅可以告诉您发生了什么事和发生在哪里(例如例外情况),还可以告诉您原因。
  4. 异常消息通常可能是含糊的,要求您向后遍历代码以重新创建导致错误的上下文。断言可以保留发生错误时程序的状态。
  5. 断言是文档的两倍,它告诉其他开发人员代码所依赖的隐含假设。
  6. 断言失败时出现的对话框使您可以将调试器附加到进程,因此可以像在其中放置断点那样在堆栈中四处戳戳。

PS:如果您喜欢Code Complete,我建议您在本书中继续阅读。我购买它是为了学习有关使用WinDBG和转储文件的知识,但前半部分包含了许多技巧,可帮助您首先避免出现bug。


3
+1为简洁而有用的摘要。非常直接适用。但是,对我而言,最主要的缺失是何时使用Trace.Assert与Trace.Assert。即关于何时/不希望它们出现在生产代码中的信息。
乔恩·库姆斯

2
JonCoombs是“ Trace.Assert与Trace.Assert”的错字吗?
Thelem

1
@thelem也许乔恩意味着Debug.AssertTrace.Assert。后者在Release版本和Debug版本中执行。
DavidRR

为什么我更喜欢Debug.Assert而不是引发异常?
巴里斯Akkurt

86

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

您仍应在调用之前引发异常Debug.Assert()。断言只是确保在您仍在开发时一切都按预期进行。


35
您能否澄清为什么在调用前仍引发异常的情况下放入断言?还是我误会了你的答案?
罗曼·斯塔科夫

2
@romkyns您仍然必须包括它们,因为如果不这样,当您在发布模式下构建项目时,所有验证/错误检查将消失。
奥斯卡·梅德罗斯

28
@Oscar我认为这首先是使用断言的全部要点...好吧,所以您将异常放在它们之前-那么为什么将断言放在后面?
罗曼·斯塔科夫

4
@superjos:我不同意:MacLeod答案中的第二点指出您确实需要断言和异常,但不在同一个地方。在变量上声明一个NullRefEx是没有用的,然后对它执行Assert(在这种情况下assert方法永远不会显示一个dialogBox,这是assert的全部内容)。MacLeod的意思是,在某些地方您需要一个例外,而在其他地方,一个Assert就足够了。
大卫

1
它可能成为凌乱我解释别人的答案:)无论如何,我跟你解释这些:你需要他们两个,你应该不会把异常之前断言。我不确定“不在同一个地方”的含义。再一次,我拒绝解释,只说出自己的想法/偏好:将一个或多个断言放在某些操作开始之前检查先决条件,或在操作之后检查后置条件。除了断言,无论如何,在断言之后,检查是否出了问题并且需要引发异常。
superjos 2012年

52

代码完成

8防御性编程

8.2断言

断言是在开发过程中使用的代码(通常是例程或宏),它使程序可以在运行时进行自我检查。当一个断言为真时,这意味着一切都按预期运行。如果为假,则表示已在代码中检测到意外错误。例如,如果系统假定客户信息文件的记录永远不会超过50,000,则该程序可能会断言记录数小于或等于50,000。只要记录数小于或等于50,000,该断言将是静默的。但是,如果遇到超过50,000条记录,它将大声“断言”程序中存在错误。

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

断言通常带有两个参数:一个布尔表达式,描述应该为真的假设;否则为要显示的消息。

(...)

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


7
那么,如果生产中遇到的客户信息文件包含超过50,000条记录,会发生什么情况?如果断言是用生产代码编译而成的,并且这种情况无法通过其他方式处理,那么这难道不是麻烦吗?
DavidRR 2015年

1
@DavidRR是的。但是,一旦生产发出问题信号,并且某些开发人员(可能不太了解此代码)调试了该问题,则声明将失败,并且开发人员将立即知道未按预期使用该系统。
2016年

48

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

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

另外,如果某些东西在运行时无法正常工作(网络错误,数据访问错误,从第三方服务检索到的不良数据等),我的私有方法仍然会引发异常。我的断言只是为了确保我没有打破自己对对象状态的内部假设。


3
这是对良好做法的非常清晰的描述,并且可以很好地回答所提出的问题。
卡斯珀·莱昂·尼尔森


31

如果我是你,我会做:

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

或避免重复条件检查

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

5
这如何解决问题?这样,debug.assert就变得毫无意义了。
Quibblesome

43
不,它不是-在抛出异常之前的那一刻,它会分解为代码。如果您在代码中的其他地方进行尝试/捕获,则您甚至可能不会注意到该异常!
马克·英格拉姆

2
+1我遇到了很多问题,人们只是不做任何事情就尝试/捕获异常,因此跟踪错误是个问题
dance2die 2009年

5
我想在某些情况下您可能想要这样做,但是您永远都不应捕获一般性的异常!
Casebash

8
@MarkIngram为您的答案-1,并为您的评论+1。对于某些特殊情况,这是一个不错的技巧,但是对于所有验证而言,这似乎是一件奇怪的事情。
Mark Amery 2013年

24

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

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

同样,如果您的应用程序以用户界面模式运行,则默认情况下将显示“断言”对话框,这可能会使您的用户有些不安。

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

综上所述,

  • 广泛使用Debug.Assert可以帮助捕获Debug版本中的错误。

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

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


1
+1指出Debug.Assert和Trace.Assert之间的关键区别,因为OP专门询问了生产代码。
乔恩·库姆斯

21

断言用于捕获程序员(您)的错误,而不是用户错误。仅当用户没有机会导致断言触发时,才应使用它们。例如,如果您正在编写API,则不应在API用户可以调用的任何方法中使用asserts来检查参数是否不为null。但是它可以在未作为API一部分公开的私有方法中使用,以断言您的代码在不应接受的情况下从不传递null参数。

当我不确定时,我通常主张例外而不是断言。


11

简而言之

Asserts 用于警卫和检查按合同约束的设计,即:

  • Asserts应该仅用于调试和非生产版本。声明通常在发行版中被编译器忽略。
  • Asserts 可以检查系统控制中的错误/意外情况
  • Asserts 不是用于用户输入或业务规则的一线验证的机制
  • Asserts应该不会被用来检测突发环境条件(这是外部的代码的控制),如超出内存,网络故障,数据库故障等。虽然罕见,这些条件是可以预期的(和你的应用程序代码无法修复问题,如硬件故障或资源耗尽)。通常,将引发异常-您的应用程序可以采取纠正措施(例如,重试数据库或网络操作,尝试释放缓存的内存),或者在无法处理异常的情况下优雅地中止。
  • 失败的声明对您的系统应该是致命的-即与异常不同,请勿尝试捕获或处理失败Asserts-您的代码在意外的区域中运行。堆栈跟踪和崩溃转储可用于确定出了什么问题。

断言具有巨大的好处:

  • 有助于查找缺少的用户输入验证或更高级别代码中的上游错误。
  • 代码库中的断言清楚地将代码中的假设传达给读者
  • 断言将在运行时在Debug构建中进行检查。
  • 一旦对代码进行了详尽的测试,将其重新构建为Release将消除验证假设的性能开销(但这样做的好处是,以后的Debug构建会在需要时始终还原检查)。

... 更多详情

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

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

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

可用的按合同设计(DBC)检查包括:

  • Contract.Requires -约定的前提条件
  • Contract.Ensures -合同规定的后期条件
  • Invariant -表示关于对象在其生命周期中所有点的状态的假设。
  • Contract.Assumes -调用非合同修饰方法时,使静态检查器平静。

不幸的是,自从MS停止开发代码合同以来,它几乎已经失效。
Mike Lowery

10

在我的书中几乎从来没有。在大多数情况下,如果您想检查一切是否正常,则抛出异常。

我不喜欢的事实是,它使调试版本与发布版本在功能上有所不同。如果调试断言失败,但是该功能在发行版中有效,那么这有什么意义呢?当断言者离开公司很久并且没人知道那部分代码时,情况会更好。然后,您必须花费一些时间来探索问题,以查看是否确实存在问题。如果有问题,那为什么不把人放在首位呢?

对我来说,这建议您使用Debug.Asserts,将问题推迟到其他人那里,亲自解决。如果应该是这种情况,那么就不会抛出。

我猜可能在一些性能至关重要的场景中,您需要优化断言,并且在这里很有用,但是我还没有遇到这样的场景。


4
您的回答值得一提,尽管您强调了有关它们的一些经常引起关注的事实,它们会中断调试会话以及出现误报的可能性。但是,您缺少一些细微之处,而是在编写“优化断言”-这只能基于认为抛出异常和执行debug.assert是相同的。并非如此,它们具有不同的目的和特征,如您在某些已获好评的答案中所见。Dw
Casper Leon Nielsen

+1”表示“我不喜欢的事实是,它使调试版本与发布版本在功能上有所不同。如果调试断言失败,但是该功能在发布版本中起作用,那么这有什么意义呢?” 在.NET中,System.Diagnostics.Trace.Assert()在Release版本和Debug版本中执行。
DavidRR 2015年

7

根据IDesign标准,您应该

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

using System.Diagnostics;

object GetObject()
{...}

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

作为免责声明,我应该提到,我认为实施此IRL不可行。但这是他们的标准。


看起来Juval Lowy喜欢引述自己。
devlord

6

仅在要为发布版本删除支票的情况下才使用断言。请记住,如果不以调试模式进行编译,则不会触发您的断言。

给定您的null检查示例,如果该示例位于仅限内部使用的API中,则可以使用断言。如果在公共API中,我肯定会使用显式检查并抛出。


在.NET中,可以用来System.Diagnostics.Trace.Assert()在发布(生产)版本中执行断言。
DavidRR

代码分析规则CA1062:验证公共方法 的参数需要检查以下情况的参数null“外部可见方法取消引用其参考参数之一,而不验证该参数是否为 null 。” 在这种情况下,方法或属性应抛出ArgumentNullException
DavidRR

6

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

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

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

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

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

在第三种情况下,此代码存在问题,因为应该en != null在调用它之前就已经对其进行了检查,以至于它不是真的是一个错误。换句话说,应该是理论上可以优化为的代码Debug.Assert(true),sicne en != null应该始终是true


1
那么,在第三种情况下,如果en == null在生产中会发生什么呢?你可能说,en == null从来没有发生在生产中(因为该方案已被彻底调试)?如果是这样,那么Debug.Assert(en != null)至少可以替代评论。当然,如果将来进行更改,它也将继续具有检测可能的回归的价值。
DavidRR 2015年

@DavidRR,我确实在断言它永远不能为null,代码中的断言也因此为名称。我当然可以错了,或者因更改而错了,这就是assert调用的价值。
乔恩·汉娜

1
Debug.Assert()在Release版本中删除对的呼叫。所以,如果你错的,在第三种情况下,你不会知道它在生产中(假设在生产过程中使用一个发布版本的)。但是,第一种情况和第二种情况的行为在Debug和Release版本中是相同的。
DavidRR

@DavidRR,这仅在我认为不可能发生时才适用,这再次是事实的断言,而不是状态检查。当然,如果有断言,有可能捕获的错误并且在测试中从未遇到这种情况,那也是没有意义的。
乔恩·汉娜

4

我以为我会再添加四种情况,其中Debug.Assert可能是正确的选择。

1)我在这里没有提到的是Asserts在自动测试期间可以提供其他概念覆盖。作为一个简单的例子:

当作者认为某些更高级别的调用者对其进行了修改后,他认为他们已经扩展了代码范围以处理其他情况,理想情况下(!),他们将编写单元测试以涵盖此新条件。然后,完全集成的代码可能会正常工作。

但是,实际上已经引入了一个细微的缺陷,但是在测试结果中并未发现。在这种情况下,被调用方已变得不确定,只能碰巧提供预期的结果。或者,它产生了未被注意的舍入误差。或导致了在其他位置均被抵消的错误。或不仅授予请求的访问权限,还授予其他不应授予的特权。等等。

在这一点上,被调用方中包含的Debug.Assert()语句以及由单元测试驱动的新案例(或边缘案例)可以在测试期间提供宝贵的通知,证明原始作者的假设已失效,并且代码不应无需额外审核即可发布。带有单元测试的断言是完美的伙伴。

2)此外,有些测试编写起来很简单,但是由于最初的假设,成本很高且不必要。例如:

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

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

假设它是嵌入式实时设备。引发异常并在遇到格式错误的数据包时重新启动会适得其反。取而代之的是,该设备可能会从尽力而为的操作中受益,甚至会在其输出中产生噪声。它也可能没有人机界面,日志记录设备,甚至当以发布模式部署时,甚至根本无法被人物理访问,并且最好通过评估相同的输出来提供对错误的认识。在这种情况下,自由声明和全面的预发布测试比例外更有价值。

4)最后,仅由于被调用方被认为是非常可靠的,所以不需要进行某些测试。在大多数情况下,可重用的代码越多,投入更多的精力使其变得可靠。因此,对于来自调用方的意外参数来说,异常是常见的,而对于来自被调用方的意外结果而言,断言是常见的。例如:

如果核心String.Find操作指出-1未找到搜索条件时它将返回a ,则您可以安全地执行一项操作,而不是三项。但是,如果它确实返回了-2,那么您可能没有合理的措施。用单独测试一个-1值的简单计算代替无用的计算,在大多数发行环境中用确保核心库按预期运行的测试来乱码,这是不合理的。在这种情况下,断言是理想的。


4

从报价采取实用主义程序员:从熟练工到硕士

保持断言处于打开状态

关于断言的常见误解是由编写编译器和语言环境的人发布的。它是这样的:

断言为代码增加了一些开销。因为它们检查了永远都不会发生的事情,所以它们只会被代码中的错误触发。一旦测试并交付了代码,就不再需要它们,应该将其关闭以使代码运行更快。断言是一种调试工具。

这里有两个明显错误的假设。首先,他们认为测试可以找到所有错误。实际上,对于任何复杂的程序,您甚至都不可能测试将要通过代码的排列的很小一部分(请参阅无情的测试)。

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

在将程序交付到生产环境时关闭断言就像在没有网络的情况下越过高线一样,因为您曾经在实践中将其跨过。有巨大的价值,但很难获得人寿保险。

即使您确实有性能问题,也请仅关闭真正触动您的断言


2

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

另外,如果您正在生产中(并具有发布版本),则抛出异常(使应用程序在最坏的情况下崩溃)比使用无效值可能会破坏客户的数据(可能要花费数千美元)更好。美元)。


1
不,与这里的其他一些答案一样:您并不真正理解其中的区别,因此选择退出其中一种产品,并在此过程中在两者之间建立了错误的二分法。Dw-
卡斯珀·莱昂·尼尔森

3
这是此列表IMO上唯一的正确答案。卡斯珀不要轻易解雇它。调试断言是一种反模式。如果在调试时不变,则在运行时不变。允许您的应用继续使用不变的不变量,将使您处于不确定状态,并且可能导致比崩溃更严重的问题。IMO最好在两个内部版本中使用相同的代码,这些代码会因合同破裂而快速失败,然后在顶层实施强大的错误处理。例如,隔离组件并实现重新启动它们的功能(例如,浏览器中的选项卡崩溃不会使整个浏览器崩溃)。
justin.m.chase

1
在此处的讨论中包括Trace.Assert可能会有所帮助,因为不能被同一论点所忽略。
乔恩·库姆斯

0

您应该使用Debug.Assert来测试程序中的逻辑错误。编译器只能通知您语法错误。因此,您应该明确地使用Assert语句来测试逻辑错误。就像说测试一个销售汽车的程序,只有蓝色的宝马才能获得15%的折扣。编译器无法告诉您程序在执行此操作时逻辑上是否正确,但是assert语句可以告诉您。


2
抱歉,例外情况都一样,因此此答案无法解决真正的问题。
罗曼·斯塔科夫

0

我已经在这里阅读了答案,并认为应该添加一个重要的区别。使用断言有两种非常不同的方式。一个临时的开发人员快捷方式是“在您的程序能够继续运行的情况下,这应该像有条件的断点”,“这应该不会真的发生,如果它确实让我知道,这样我就可以决定要做什么”。另一种是在代码中放置有关有效程序状态的假设的方法。

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

但是在第二种情况下,断言是代码的一部分。他们断言您的假设是正确的,并记录在案。在这种情况下,您真的想将它们留在代码中。如果程序处于无效状态,则不应继续执行该程序。如果您负担不起性能方面的损失,那么您就不会使用C#。一方面,如果发生这种情况,能够附加调试器可能会很有用。另一方面,您不希望在用户上弹出堆栈跟踪,也许更重要的是,您不希望他们能够忽略它。此外,如果它在服务中,它将始终被忽略。因此,在生产中,正确的行为是引发异常,并使用程序的常规异常处理,这可能会向用户显示一个不错的消息并记录详细信息。

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>

-1

我不知道它在C#和.NET中的情况如何,但是在C中,assert()仅在使用-DDEBUG编译时才起作用-如果不进行编译,则最终用户将永远看不到assert()。仅适用于开发人员。我真的经常使用它,有时更容易跟踪错误。


-1

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.