Debug.Assert与exception抛出

我已经阅读了大量关于如何以及何时使用断言的文章 (以及发布在StackOverflow上的其他类似问题),并且我很好地理解了它们。 但是,我不明白什么样的动机应该促使我使用Debug.Assert而不是抛出一个普通的exception。 我的意思是,在.NET中,对失败断言的默认响应是“停止世界”并向用户显示一个消息框。 虽然这种行为可以修改,但是我觉得这样做是非常烦人和多余的,而我却可以抛出一个合适的例外。 这样,我就可以在抛出exception之前轻松地将错误写入应用程序的日志,而且,我的应用程序不一定会冻结。

那么,为什么我应该使用Debug.Assert而不是普通的exception呢? 在不应该的地方放置一个断言只会引起各种各样的“不需要的行为”,所以在我看来,通过使用断言而不是抛出exception,我实在没有获得任何东西。 你是否同意我的观点?或者我在这里错过了什么?

注意:我完全理解“理论上”(debuggingvs释放,使用模式等)有什么区别,但就我所见,我最好抛出exception而不是执行断言。 因为如果在产品发布中发现错误,我仍然希望“断言”会失败(毕竟,“开销”是可笑的小),所以我会更好抛出一个exception。


编辑:我看到它的方式,如果断言失败,这意味着应用程序进入某种损坏,意外的状态。 那为什么我要继续执行? 如果应用程序在debugging版本或发行版本上运行,则无关紧要。 两者都是一样的

虽然我同意你的推理是合理的 – 也就是说,如果一个断言被意外地违反了,那么通过抛出来停止执行是有道理的 – 我个人不会在断言的地方使用exception。 原因如下:

正如其他人所说,断言应logging 不可能的情况 ,以这样的方式,如果所谓的不可能的情况发生,开发人员被告知。 相反,例外情况为特殊的,不可能的或错误的情况提供了控制stream程机制,但不是不可能的情况。 对我来说,关键的区别在于:

  • 应该总是有可能产生一个testing案例,执行一个给定的投掷陈述。 如果不可能产生这样的testing用例,那么你的程序中有一个永远不会执行的代码path,应该作为死代码去除。

  • 应该永远不可能产生一个引发断言的testing用例。 如果一个断言触发了,代码是错误的或者断言是错误的; 无论如何,在代码中需要改变一些东西。

这就是为什么我不会用一个exception来replace一个断言。 如果断言不能实际触发,那么用exceptionreplace意味着你的程序中有一个不可testing的代码path 。 我不喜欢不可testing的代码path。

断言用于检查程序员对世界的理解。 只有程序员做错了,断言才会失败。 例如,不要使用断言来检查用户input。

声明testing条件“不可能发生”。 例外情况是“不应该发生,但是”的情况。

断言是有用的,因为在构build时(甚至运行时)你可以改变他们的行为。 例如,通常在发布版本中,断言甚至不被检查,因为它们引入了不必要的开销。 这也是值得警惕的:你的testing甚至可能不会被执行。

如果您使用exception而不是断言,则会失去一些价值:

  1. 代码更冗长,因为testing和抛出exception至less有两行,而断言只有一行。

  2. 你的testing和抛出代码将会一直运行,而断言可以被编译掉。

  3. 您与其他开发人员失去了一些沟通,因为断言与产品代码有不同的含义。 如果你真的正在testing一个编程断言,使用一个断言。

更多在这里: http : //nedbatchelder.com/text/assert.html

编辑: 为了响应你在你的文章中所做的编辑/注释:这听起来像使用exception是正确的事情来使用断言的types,你正试图完成的事情。 我认为,你遇到的心理障碍是你正在考虑例外和断言来达到同样的目的,所以你正在试图找出哪一个“正确”使用。 虽然在如何使用断言和例外方面可能会有一些重叠,但不要把它们混为一谈,因为它们对于同一个问题是不同的解决scheme – 它们不是。 断言和例外都有自己的目的,优势和弱点。

我会用自己的话来回答一个问题,但是这样做的概念比我想象的更好:

C#站:断言

使用assert语句可以是在运行时捕获程序逻辑错误的有效方法,但它们很容易被过滤掉生产代码。 一旦开发完成,编译错误的这些冗余testing的运行时成本可以简单地通过在编译期间定义预处理器符号NDEBUG [其禁用所有断言]来消除。 但是请记住,在生产版本中,放置在assert中的代码将被省略。

只有当以下所有条件成立时,断言才最适用于testing条件:

 * the condition should never be false if the code is correct, * the condition is not so trivial so as to obviously be always true, and * the condition is in some sense internal to a body of software. 

几乎不应该使用断言来检测软件正常运行时出现的情况。 例如,通常断言不应该用来检查用户input中的错误。 然而,使用断言来validation调用者已经检查了用户的input可能是有意义的。

基本上,对于需要在生产应用程序中捕获/处理的事物使用exception,使用断言来执行对开发有用的逻辑检查,但在生产中closures。

Code Complete的另一个块:

“一个断言是一个函数或macros,如果假设不是真的,就会大声抱怨。使用断言来logging代码中所做的假设,并消除意外情况。

“在发展过程中,断言冲突了矛盾的假设,意外的情况,传递给日常事务的不良价值,等等。

他继续增加一些应该和不应该声称的指导方针。

另一方面,例外:

“使用exception处理来引起对意外情况的注意,特殊情况下的处理方式应使其在开发过程中显而易见,并且在生产代码运行时可以恢复。

如果你没有这本书,你应该买它。

我认为一个(做作的)实际例子可能有助于说明这个区别:

(改编自MoreLinq的批量扩展 )

 // 'public facing' method public int DoSomething(List<string> stuff, object doohickey, int limit) { // validate user input and report problems externally with exceptions if(stuff == null) throw new ArgumentNullException("stuff"); if(doohickey == null) throw new ArgumentNullException("doohickey"); if(limit <= 0) throw new ArgumentOutOfRangeException("limit", limit, "Should be > 0"); return DoSomethingImpl(stuff, doohickey, limit); } // 'developer only' method private static int DoSomethingImpl(List<string> stuff, object doohickey, int limit) { // validate input that should only come from other programming methods // which we have control over (eg we already validated user input in // the calling method above), so anything using this method shouldn't // need to report problems externally, and compilation mode can remove // this "unnecessary" check from production Debug.Assert(stuff != null); Debug.Assert(doohickey != null); Debug.Assert(limit > 0); /* now do the actual work... */ } 

正如Eric Lippert等人所说的,你只是断言你期望正确的东西,以防万一 (开发者)意外地在别的地方使用它 ,所以你可以修复你的代码。 当你无法控制或者无法预知会发生什么时( 例如用户input) ,你基本上会抛出exception,这样任何给出错误数据的东西都可以正确响应(例如用户)。

默认情况下,Debug.Assert只能在debugging版本中工作,所以如果你想要在你的发布版本中捕获任何不良的意外行为,你需要使用exception或者打开项目属性中的debugging常量(这被认为是一般不是一个好主意)。