Nic*_*uso 217 language-agnostic testing defensive-programming exception assertions
我已经是一名专业软件工程师,已经有一年的时间了,已经获得了CS学位.我已经知道C++和C中有一段时间的断言,但直到最近才知道它们在C#和.NET中存在.
我们的生产代码不包含任何断言,我的问题是......
我应该在生产代码中开始使用Asserts吗?如果是这样,它的使用何时最合适?这样做会更有意义吗?
Debug.Assert(val != null);
Run Code Online (Sandbox Code Playgroud)
要么
if ( val == null )
throw new exception();
Run Code Online (Sandbox Code Playgroud)
Ror*_*eod 224
在调试Microsoft .NET 2.0应用程序中, John Robbins有一个关于断言的重要部分.他的要点是:
PS:如果您喜欢Code Complete,我建议您继续阅读本书.我买它是为了学习使用WinDBG和转储文件,但上半部分包含了一些提示,以帮助避免错误.
Mar*_*ade 86
将Debug.Assert()所有代码放在您想要的代码中进行健全性检查以确保不变量.编译Release版本(即没有DEBUG编译器常量)时,Debug.Assert()将删除调用,因此它们不会影响性能.
你应该在调用之前抛出异常Debug.Assert().断言只是确保在你还在开发的时候,一切都如预期的那样.
jua*_*uan 51
从代码完成
8防御性编程
8.2断言
断言是在开发期间使用的代码 - 通常是例程或宏 - 允许程序在运行时检查自身.当断言为真时,这意味着一切都按预期运行.当它为假时,这意味着它已在代码中检测到意外错误.例如,如果系统假定客户信息文件永远不会有超过50,000条记录,则程序可能包含一条断言,即记录数量小于或等于50,000.只要记录数小于或等于50,000,断言就会保持沉默.但是,如果它遇到超过50,000条记录,它将大声"断言"程序中存在错误.
断言在大型复杂程序和高可靠性程序中特别有用.它们使程序员能够更快地清除不匹配的接口假设,修改代码时出现的错误,等等.
断言通常需要两个参数:一个布尔表达式,用于描述假定应该为true的假设,以及一个要显示的消息(如果不是).
(......)
通常,您不希望用户在生产代码中看到断言消息; 断言主要用于开发和维护期间.断言通常在开发时编译到代码中,并从代码中编译生产.在开发过程中,断言会清除相互矛盾的假设,意外情况,传递给例程的错误值等等.在生产过程中,它们是从代码中编译出来的,因此断言不会降低系统性能.
Nic*_*cki 46
FWIW ...我发现我的公共方法倾向于使用if () { throw; }模式来确保正确调用该方法.我的私人方法倾向于使用Debug.Assert().
我的想法是,使用我的私有方法,我是一个受控制的人,所以如果我开始使用不正确的参数调用我自己的私有方法之一,那么我已经在某个地方打破了我自己的假设 - 我应该从未得到过进入那个州.在生产中,这些私有断言理想上应该是不必要的工作,因为我应该保持我的内部状态有效和一致.与给予公共方法的参数对比,任何人都可以在运行时调用这些参数:我仍然需要通过抛出异常来强制执行参数约束.
此外,如果某些内容在运行时不起作用(网络错误,数据访问错误,从第三方服务检索到的错误数据等),我的私有方法仍然可以抛出异常.我的断言只是为了确保我没有打破我自己关于对象状态的内部假设.
Mar*_*ram 32
如果我是你,我会这样做:
Debug.Assert(val != null);
if ( val == null )
throw new exception();
Run Code Online (Sandbox Code Playgroud)
或者避免重复检查
if ( val == null )
{
Debug.Assert(false,"breakpoint if val== null");
throw new exception();
}
Run Code Online (Sandbox Code Playgroud)
Joe*_*Joe 23
如果您希望在生产代码中使用Asserts(即Release版本),则可以使用Trace.Assert而不是Debug.Assert.
这当然会增加生产可执行文件的开销.
此外,如果您的应用程序在用户界面模式下运行,默认情况下将显示"断言"对话框,这可能会让您的用户感到有些不安.
您可以通过删除DefaultTraceListener来覆盖此行为:请查看MSDN中Trace.Listeners的文档.
综上所述,
使用Debug.Assert可以帮助捕获Debug构建中的错误.
如果在用户界面模式下使用Trace.Assert,则可能需要删除DefaultTraceListener以避免使用户感到不安.
如果您正在测试的条件是您的应用无法处理的情况,那么最好不要抛出异常,以确保执行不会继续.请注意,用户可以选择忽略断言.
use*_*113 21
断言用于捕获程序员(您的)错误,而不是用户错误.只有在用户无法触发断言时才应使用它们.例如,如果您正在编写API,则不应使用断言来检查API用户可以调用的任何方法中的参数是否为空.但是它可以在一个私有方法中使用,而不是作为API的一部分公开,以断言你的代码在它不应该传递时永远不会传递null参数.
当我不确定时,我通常偏爱断言而不是断言.
Stu*_*tLC 11
简而言之
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 - 在调用非合约修饰方法时,静默静态检查程序.Qui*_*ome 10
绝大多数都不在我的书中.在绝大多数情况下,如果你想检查一切是否理智,那么扔掉,如果不是.
我不喜欢的是它使调试版本在功能上与发布版本不同.如果调试断言失败但功能在发布中有效,那么这有什么意义呢?当断言器长期离开公司并且没有人知道代码的那部分时,它会更好.然后,您必须花费一些时间来探索问题,看看它是否真的是一个问题.如果这是一个问题那么为什么不是第一个投掷的人呢?
对我来说,这表明使用Debug.Asserts你将问题推迟给别人,自己处理问题.如果事情应该是这样的话,那就不会抛出.
我想有可能是性能关键的场景,你想要优化你的断言,它们在那里很有用,但是我还没有遇到这样的场景.
根据IDesign标准,你应该
断言每一个假设.平均而言,每五行就是一个断言.
using System.Diagnostics;
object GetObject()
{...}
object someObject = GetObject();
Debug.Assert(someObject != null);
Run Code Online (Sandbox Code Playgroud)
作为免责声明,我应该提到我没有发现实施这个IRL是否切合实际.但这是他们的标准.
仅在需要为发布版本删除检查的情况下使用断言.请记住,如果不在调试模式下编译,则不会触发断言.
给出check-for-null示例,如果这是在仅内部API中,我可能会使用断言.如果它在公共API中,我肯定会使用显式检查和抛出.
所有断言都应该是可以优化的代码:
Debug.Assert(true);
Run Code Online (Sandbox Code Playgroud)
因为它正在检查你已经假设的东西是真的.例如:
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());
}
Run Code Online (Sandbox Code Playgroud)
在上文中,null参数有三种不同的方法.第一个接受它是允许的(它什么都不做).第二个抛出调用代码处理的异常(或不抛出,导致错误消息).第三个假设它不可能发生,并断言它是如此.
在第一种情况下,没有问题.
在第二种情况下,调用代码存在问题 - 它不应该GetFirstAndConsume使用null 调用,因此它会返回异常.
在第三种情况下,这个代码存在一个问题,因为en != null在它被调用之前它应该已经被检查过,所以它不是真的是一个错误.或者换句话说,它应该是理论上可以优化的代码Debug.Assert(true),sicne en != null应该永远是true!
启用断言
编写编译器和语言环境的人对断言存在一种常见的误解。事情是这样的:
断言会增加代码的一些开销。因为它们检查不应该发生的事情,所以它们只会被代码中的错误触发。一旦代码经过测试和交付,就不再需要它们,应该关闭它们以使代码运行得更快。断言是一种调试工具。
这里有两个明显错误的假设。首先,他们假设测试发现了所有错误。实际上,对于任何复杂的程序,您都不太可能测试代码将要经历的排列的一小部分(请参阅无情测试)。
其次,乐观主义者忘记了你的程序运行在一个危险的世界中。在测试过程中,老鼠可能不会咬断通信电缆,玩游戏的人不会耗尽内存,日志文件也不会填满硬盘。当您的程序在生产环境中运行时,可能会发生这些情况。您的第一道防线是检查任何可能的错误,第二道防线是使用断言来尝试检测您错过的错误。
当您将程序交付生产时关闭断言就像跨越没有网的高空钢丝,因为您曾经在实践中跨越过它。它具有巨大的价值,但很难获得人寿保险。
即使您确实存在性能问题,也只关闭那些真正影响您的断言。
| 归档时间: |
|
| 查看次数: |
52143 次 |
| 最近记录: |