我已经阅读了很多关于如何以及何时使用断言的articles(以及其他一些发布在StackOverflow上的类似的问题),我对它们理解得很清楚。但是,我仍然不明白什么样的动机应该促使我使用Debug.Assert
而不是抛出一个普通的异常。我的意思是,在.NET中,对失败的断言的默认响应是“停止世界”并向用户显示一个消息框。虽然可以修改这种行为,但我发现它非常烦人且多余
要做到这一点,我可以改为,只是抛出一个合适的例外。这样,我可以在抛出异常之前轻松地将错误写入应用程序的日志,而且,我的应用程序不一定会冻结。
那么,为什么我应该使用Debug.Assert
而不是普通的例外呢?将断言放在不应该出现的地方可能会导致各种“不需要的行为”,所以在我看来,我真的没有通过使用断言而不是抛出异常来获得任何东西。你同意我的观点,还是我在这里遗漏了什么?
注意:我完全理解“在理论上”的区别(调试与发布,使用模式等),但正如我所看到的,我最好不要抛出异常而不是执行断言。因为如果在生产版本上发现了一个bug,我仍然希望“断言”失败(毕竟,“开销”非常小),所以我最好不要抛出异常。
编辑:我看到它的方式,如果断言失败,这意味着应用程序进入了某种已损坏的意外状态。那么我为什么要继续执行呢?应用程序是否在调试版或发行版上运行并不重要。
也是如此答案 0 :(得分:163)
虽然我同意你的推理是似乎合理的 - 也就是说,如果一个断言意外地被违反,那么通过抛出来暂停执行是有意义的 - 我个人不会使用例外代替断言。原因如下:
正如其他人所说的那样,断言应记录 的情况,以便如果所谓的不可能的情况发生,开发人员会被告知。相反,例外情况为异常,不太可能或错误的情况提供了控制流机制,但并非不可能的情况。对我来说,关键的区别在于:
应该始终可以生成一个测试用例来执行给定的throw语句。如果无法生成这样的测试用例,那么程序中的代码路径永远不会执行,并且应该作为死代码删除。
永远不可能产生导致断言发射的测试用例。如果断言触发,则代码错误或断言错误;无论哪种方式,都需要在代码中进行更改。
这就是为什么我不会用异常替换断言。如果断言实际上无法触发,则将其替换为异常意味着您的程序中存在不可测试的代码路径。我不喜欢不可测试的代码路径。
答案 1 :(得分:15)
断言用于检查程序员对世界的理解。只有程序员做错了,断言才会失败。例如,永远不要使用断言来检查用户输入。
断言测试“不可能发生”的情况。例外情况是“不应该发生但是要做”的条件。
断言很有用,因为在构建时(甚至运行时),您可以更改其行为。例如,通常在发布版本中,甚至不检查断言,因为它们会引入不必要的开销。这也是需要警惕的:你的测试甚至可能都没有被执行。
如果使用异常而不是断言,则会丢失一些值:
代码更详细,因为测试和抛出异常至少有两行,而断言只有一行。
您的测试和抛出代码将始终运行,而断言可以编译掉。
您失去了与其他开发人员的沟通,因为断言与检查和抛出的产品代码具有不同的含义。如果您确实在测试编程断言,请使用断言。
答案 2 :(得分:11)
修改强> 回复您在帖子中所做的编辑/记录: 听起来使用异常是正确的事情,使用断言来表示你想要完成的事情类型。 我认为你遇到的心理障碍是你正在考虑例外和断言来实现同样的目的,所以你试图弄清楚哪一个是“正确的”使用。虽然在如何使用断言和例外方面可能存在一些重叠,但不要混淆它们对于同一问题的不同解决方案 - 它们不是。断言和例外都有自己的目的,优点和缺点。
我打算用我自己的话说出一个答案,但这样做的概念比我的理念更好:
使用assert语句可以是一个 捕获程序逻辑的有效方法 运行时的错误,但它们是 很容易过滤掉生产 码。一旦开发完成, 这些冗余的运行时成本 编码错误的测试可以 简单地通过定义来消除 预处理器符号NDEBUG [其中 禁用所有断言]期间 汇编。但是,请确保 记住放在的代码 断言本身将被省略 生产版。
断言最好用于测试a 条件只有当所有的 以下举行:
* 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.
断言几乎不应用于检测出现的情况 在软件正常运行期间。 例如,通常断言应该 不用于检查a中的错误 用户的输入。然而,它可能会 感觉使用断言来验证 呼叫者已经检查过用户 输入
基本上,对于需要在生产应用程序中捕获/处理的事物使用异常,使用断言来执行逻辑检查,这对于开发有用但在生产中关闭。
答案 3 :(得分:6)
我认为一个(人为的)实际例子可能有助于阐明差异:
(改编自MoreLinq's Batch extension)
// '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... */
}
正如Eric Lippert等人所说的那样,你只断言你期望是正确的东西,以防万一 你(开发者)意外地使用它错了其他地方,所以你可以修复你的代码。当您无法控制或无法预测到的内容时,您基本上会抛出异常,例如。对于用户输入,所以无论什么给它带来不好的数据都可以适当地响应(例如用户)。
答案 4 :(得分:4)
来自Code Complete的另一个金块:
“断言是一个函数或宏 如果有一个假设,那就大声抱怨 不是真的。使用断言进行记录 代码中的假设和冲洗 出乎意料的情况。 ...
“在开发过程中,断言冲洗 出于矛盾的假设, 意外情况,糟糕的价值观 传递给惯例,等等。“
他继续为应该和不应该断言的内容添加一些指导。
另一方面,例外:
“使用异常处理来绘制 注意意外情况。 应该处理特殊情况 一种让他们明显的方式 发展和可恢复的时候 生产代码正在运行。“
如果你没有这本书,你应该买它。
答案 5 :(得分:0)
默认情况下,Debug.Assert仅适用于调试版本,因此如果要在发布版本中捕获任何类型的错误意外行为,则需要在项目属性中使用异常或打开调试常量(一般认为不是一个好主意。)
答案 6 :(得分:0)
对于 ARE 可能但不应该发生的事情使用断言(如果不可能,为什么要提出断言?)。
这听起来不像使用Exception
的情况吗?为什么要使用断言而不是Exception
?
因为应该在断言之前调用代码来阻止断言的参数为false。
通常在Exception
之前没有代码可以保证它不会被抛出。
为什么Debug.Assert()
在prod中编译好了?如果您想在调试中了解它,您不想在prod中了解它吗?
您只希望在开发期间使用它,因为一旦找到Debug.Assert(false)
情况,您就编写代码以保证Debug.Assert(false)
不会再次发生。
一旦开发完成,假设您已找到Debug.Assert(false)
情况并修复它们,Debug.Assert()
可以安全地编译掉,因为它们现在是多余的。
答案 7 :(得分:0)
假设您是一个相当大的团队的成员,并且有几个人都在相同的通用代码库上工作,包括在类上重叠。 您可以创建一个由其他几种方法调用的方法,并且为了避免锁争用,您无需向其添加单独的锁,而是“假定”该方法先前已由具有特定锁的调用方法锁定。 如, Debug.Assert(RepositoryLock.IsReadLockHeld || RepositoryLock.IsWriteLockHeld); 其他开发人员可能会忽略注释,即调用方法必须使用锁,但他们不能忽略此注释。