Debug.Assert vs Exception Throwing

91 c# exception assertions

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

那么,为什么我应该使用Debug.Assert而不是普通的例外呢?将断言放在不应该出现的地方可能会导致各种"不需要的行为",所以在我看来,我真的没有通过使用断言而不是抛出异常来获得任何东西.你同意我的意见,还是我在这里遗漏了什么?

注意:我完全理解"在理论上"(调试与发布,使用模式等)之间的区别是什么,但正如我所看到的,我最好抛出异常而不是执行断言.因为如果在生产版本上发现了一个bug,我仍然希望"断言"失败(毕竟,"开销"非常小),所以我最好不要抛出异常.


编辑:我看到它的方式,如果断言失败,这意味着应用程序进入某种已损坏的意外状态.那么我为什么要继续执行呢?应用程序是否在调试版或发行版上运行并不重要.两者都是如此

Eri*_*ert 169

虽然我同意你的推理是合理的 - 也就是说,如果一个断言意外地被违反,通过抛出来暂停执行是有道理的 - 我个人不会在断言的位置使用异常.原因如下:

正如其他人所说,主张应记录那些情况是不可能的,以这样的方式,如果涉嫌不可能的情况来传递,开发商通知.相反,例外情况为异常,不太可能或错误的情况提供了控制流机制,但并非不可能的情况.对我来说,关键的区别在于:

  • 始终应该可以生成一个测试用例来执行给定的throw语句.如果无法生成这样的测试用例,那么程序中的代码路径将永远不会执行,并且应该作为死代码删除.

  • 永远不可能产生一个导致断言发射的测试用例.如果断言触发,则代码错误或断言错误; 无论哪种方式,都需要在代码中进行更改.

这就是为什么我不会用异常替换断言.如果断言实际上无法触发,则将其替换为异常意味着您的程序中存在不可测试的代码路径.我不喜欢不可测试的代码路径.

  • 断言的问题是它们不在生产构建中.假设条件失败意味着您的程序已进入未定义的行为区域,在这种情况下,负责的程序必须尽快停止执行(展开堆栈也有些危险,具体取决于您想要获得的严格程度).是的,断言通常应该是不可能的,但你不知道什么事情在野外消失时是可能的.您认为__不可能_可能会在生产中发生,而负责任的计划应检测违反的假设并立即采取行动. (16认同)
  • @ kixxx2:这是C#,因此您可以使用Trace.Assert*在生产代码中保留断言.您甚至可以使用app.config文件将生产断言重定向到文本文件,而不是对最终用户粗鲁. (4认同)
  • @EricLippert那么在理论编程语言中,每个程序员的错误都会导致编译时错误,不需要断言吗?比如说,在 Rust 中,没有人需要断言与内存管理相关的任何内容,因为编译器将确保所讨论的某些情况实际上是不可能的?在 C# 中,只要按照预期使用多态性和泛型,就不需要断言正确的类型,因此它是类型安全的?所以有人可以说断言是一种工具,可以保护程序员免受所使用的编程语言无法保护他们的难以推理的情况的影响? (4认同)
  • @BrunoZell:是的,这是一个很好的总结。在 C# 中,您可能会编写一个字符串变量不为 null 的断言,但您永远不会编写一个字符串变量不是装箱整数的断言;你为什么会这样做?该程序要么未编译,要么在赋值尝试时抛出异常。断言表达了语言*不*强制执行的不变量。 (4认同)
  • @ kizzx2:好的,那么你写的每行生产代码有多少不可能的例外? (2认同)
  • @AnorZaken:您的观点说明了例外情况中的设计缺陷.正如我在其他地方所指出的那样,例外是(1)致命灾难,(2)永远不会发生的愚蠢错误,(3)设计失败,其中异常用于表示非异常情况,或(4)意外的外生条件.为什么这四个完全不同的东西都是例外的?如果我有我的druthers,愚蠢的"null被取消引用"异常将无法捕获**.它永远不会*正确,它应该*在它造成更多伤害之前终止你的程序*.他们应该更像是断言. (2认同)
  • @Backwards_Dave:*假* 断言是坏的,不是真的。断言使您能够运行您不想在生产中运行的昂贵的验证检查。如果在生产中违反了断言,最终用户应该怎么做? (2认同)

Ned*_*der 15

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

断言测试"不可能发生"的情况.例外情况是"不应该发生而是发生"的情况.

断言很有用,因为在构建时(甚至运行时),您可以更改其行为.例如,通常在发布版本中,甚至不检查断言,因为它们会引入不必要的开销.这也是需要警惕的:您的测试甚至可能无法执行.

如果使用异常而不是断言,则会丢失一些值:

  1. 代码更冗长,因为测试和抛出异常至少有两行,而断言只有一行.

  2. 您的测试和抛出代码将始终运行,而断言可以编译掉.

  3. 您失去了与其他开发人员的一些沟通,因为断言与检查和抛出的产品代码具有不同的含义.如果您确实在测试编程断言,请使用断言.

更多信息:http://nedbatchelder.com/text/assert.html

  • “不可能发生”用引号引起来是有原因的:只有程序员在程序的另一部分做错了,它才会发生。断言是对程序员错误的检查。 (2认同)

Tom*_*and 11

编辑: 响应您在帖子中所做的编辑/注释:听起来使用异常是正确的事情,使用断言来表示您想要完成的事情类型.我认为你遇到的心理障碍是你正在考虑例外和断言来实现同样的目的,所以你试图弄清楚哪一个是"正确的"使用.虽然在如何使用断言和例外方面可能存在一些重叠,但不要混淆它们对于同一问题的不同解决方案 - 它们不是.断言和例外都有自己的目的,优点和缺点.

我打算用我自己的话说出一个答案,但这样做的概念比我的理念更好:

C#站:断言

使用assert语句可以是在运行时捕获程序逻辑错误的有效方法,但它们很容易从生产代码中过滤掉.一旦开发完成,只需在编译期间定义预处理器符号NDEBUG [禁用所有断言],就可以消除这些编码错误冗余测试的运行时成本.但是,请务必记住,在生产版本中将省略置于断言本身的代码.

断言最好用于仅在以下所有条件成立时测试条件:

* 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.
Run Code Online (Sandbox Code Playgroud)

断言几乎不应用于检测软件正常运行期间出现的情况. 例如,通常不应使用断言来检查用户输入中的错误.但是,使用断言来验证调用者是否已经检查了用户的输入可能是有意义的.

基本上,对生产应用程序中需要捕获/处理的事物使用异常,使用断言执行逻辑检查,这些检查对于开发很有用,但在生产中关闭.


drz*_*aus 6

我认为一个(人为的)实际例子可能有助于阐明差异:

(改编自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 (e.g. 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... */
}
Run Code Online (Sandbox Code Playgroud)

正如Eric Lippert等人所说,你只断言你期望是正确的东西,以防(开发人员)在其他地方意外地使用它,所以你可以修复你的代码.当您无法控制或无法预测进入的内容时,您基本上会抛出异常,例如对于用户输入,以便无论什么给它带来坏数据都可以适当地响应(例如用户).

  • 这就是重点——断言是为了记录不可能的事情。为什么要这么做?因为您可能有像 ReSharper 这样的东西,它会在 DoSomethingImpl 方法中警告您“您可能在此处取消引用 null”,并且您想告诉它“我知道我在做什么,这永远不能为 null”。这对于一些后来的程序员来说也是一种暗示,他们可能不会立即意识到 DoSomething 和 DoSomethingImpl 之间的联系,尤其是当它们相距数百行时。 (3认同)