Debug.Assert 与异常

Posted

技术标签:

【中文标题】Debug.Assert 与异常【英文标题】:Debug.Assert vs Exceptions 【发布时间】:2009-02-03 07:56:28 【问题描述】:

令人惊讶的是,我只能找到关于这个主题的一个关于 SO 的先前问题,我只想让社区对我的方法进行“信任投票”(或不投票!)。

我的看法是这样的:

使用Debug.Assert 来陈述您期望为真的事情。这将在我们完全控制我们的环境时使用,例如在一种方法中 验证一些前置条件和后置条件。 在出现异常情况时使用异常。处理外部资源,即文件、数据库、网络等是轻而易举的事。但是...

在下面的场景中它变得有点模糊。请注意,这是一个人为的示例,仅用于说明!

假设我们有一个 MyClass 类,它有一个公共属性 MyMode 和一个方法 GetSomeValueForCurrentMode()。将 MyClass 视为一个打算在库中交付(构建发布)以供其他开发人员使用的类。

我们希望 MyMode 由此类的外部用户更新。现在,GetSomeValueForCurrentMode() 的逻辑如下:

switch(MyMode)

case Mode.ModeA:
return val1;
case Mode.ModeB:
return val2;
default:
//Uh-uh this should never happen


我在这里得到的是 MyClass 的用户已将其置于无效状态。那我们该怎么办呢?

默认情况下,我们应该Debug.Assert还是throw new InvalidOperationException(或其他)?

有一个口头禅说我们不应该信任我们课程的用户。如果我们选择 Debug.Assert 并将 MyClass 构建为发布版本(从而删除 Debug Asserts),则该类的用户不会获得他们将其置于无效状态的有用信息。但这有点与另一个口头禅相反,后者说只有在完全无法控制的事情发生时才抛出异常。

我发现我在这个问题上转了一圈 - 那些似乎没有明确“正确”答案的编程辩论之一。所以让我们把它付诸表决吧!

编辑:我在一个相关的 SO 问题 (Design by contract using assertions or exceptions?) 中注意到了这个回复:

经验法则是,当你试图捕捉自己的错误时,你应该使用断言,而当你试图捕捉别人的错误时,你应该使用异常。换句话说,您应该使用异常来检查公共 API 函数的先决条件,以及每当您获得系统外部的任何数据时。您应该为系统内部的函数或数据使用断言。

对我来说,这是有道理的,并且可以与下面概述的“断言然后抛出”技术结合使用。

欢迎提出想法!

【问题讨论】:

【参考方案1】:

我同意这里大多数人的观点,并遵循按合同设计。您应该尝试清楚地区分已部署代码中的需求(合同)和设计期间的预期状态(调试断言)。

您应该始终将合同断言作为异常抛出(因为它们应该始终是异常的)。大多数框架都内置了用于捕获调试断言的机制。但是在运行时你应该总是抛出一个异常。

我使用自定义库来帮助解决这个问题(在 C#/VB.NET 中)。如果您对它在实践中的工作方式感兴趣,我最近将它放在 Codeplex (http://www.contractdriven.com/) 上。

这样做的一个附带好处是,随着您开始更频繁地使用 DbC,您很少需要使用调试断言,因为您的代码中已经写入了明确的保证,因此实际上很难进入无效状态。

所以你原来的帖子中的问题......“我在这里得到的是 MyClass 的用户让它处于无效状态。那么我们应该怎么做?”......永远不会出现。

您可能再也不需要调试任何东西了! ;-)

【讨论】:

克里斯,抛出一个“合约损坏”异常(它只能表示一个错误)不是“快速失败”,而是“中等快速失败”:必须首先进行大量堆栈展开。当你发现违约时立即中止将是真正的“快速失败”。 Y.对不起大家。我删除了对“快速失败”的引用。如果你想 FailFast 你可以在 C# 中使用 Environment.FailFast() 而不是抛出异常。但请注意,您的 finally 块不会运行。 考虑使用fluentassertions。它有助于创建可读的合同,同时如果违反合同,它会抛出异常。【参考方案2】:

首先,MyClass 的有效性当然应该由 MyClass 的invariant来表示。

其次,您说“我们希望 MyMode 由此类的外部用户更新” - 当然,此模式的设置器应该具有典型的按合同设计的形式(与任何公共函数一样):

  void Setter(mode m)
  
    // INVARIANT ASSERT (1)
    // PRECONDITION ASSERTS (uses "m") (2)

    // BODY (3)

    // POSTCONDITION ASSERTS (if any) (4)
    // INVARIANT ASSERT (5)
  

在 (5) 中,您将因不变量不成立的尖叫断言违规而失败。 但在 (2) 中,您会失败,因为传递的 模式 m 无效。这会向用户发送明确的消息,从而解决您的问题。 p>

请不要告诉我模式字段是公开的,用户在没有任何控制的情况下更改它。

编辑:关于断言和释放模式,另见:

Are assertions always bad?

When should assertions stay in production code?

Design by contract using assertions or exceptions?

【讨论】:

【参考方案3】:

我基本上同意你自己的问题的结论:如果 Alice 的代码检测到 Alice 犯了一个错误,那就是 Assert(除非性能另有规定,否则断言应保留在生产代码中)。如果 Alice 的代码在 Eve 的代码中检测到错误,则可能是 E异常,假设 Alice 和 Eve 在您的错误跟踪软件的对立面

现在这是一般的经验法则。断言,以稍微修改的形式,也可以用作“heads-up,developer!”机制(然后它们不应该被称为“ASSERT”,而是“HEADS_UP”或类似的东西)。如果您的公司开发了客户端/服务器产品,而服务器向客户端发送了无效数据怎么办?如果您是客户端程序员,您想将其视为外部数据(即 Eve 的数据是 kaputt)并且您想抛出异常。但是一个“更软”的断言,它使 Visual Studio 的调试器停在那里,可能是一个非常早地检测这些问题并将其报告给服务器团队的好方法。在实际安装中,很可能是 Mallory 对 Eve 和 Alice 之间的数据进行调和,但大多数时候这确实是你的一位同事的错误,你想在它发生时看到它——这就是我打电话给他们的原因“heads-up”断言:它们不会替代异常,但它们会给你一个警告和一个检查问题的机会。

【讨论】:

【参考方案4】:

通常两者兼有:断言,然后抛出。

您断言是因为您想在开发过程中通知开发人员一个错误的假设。

您抛出,因为如果在发布版本中发生这种情况,您需要确保系统在处于错误状态时不会继续处理。

您的系统所需的可靠性特性可能会影响您在此处的选择,但我认为“断言然后抛出”通常是一种有用的策略。

【讨论】:

谢谢,这很有意义,与我的想法相似。我会留给 SO 社区来决定他们是否同意! 布莱恩,我不同意。断言可以帮助您测试代码。如果您在生产环境中跳过了断言,则意味着您有一个未经测试的代码路径,并且无法保证您的异常处理在堆栈中更进一步地经过测试。 是的。系统的可靠性特性再次发挥作用。如果您正在使用 Web 服务器,您可能已经设计并审查了系统的异常处理(以保持服务器正常运行,但允许一个消息/会话出现问题),在这种情况下,您可能会遇到这种情况。 .. ...异常处理程序绝对是经过良好测试的合理期望(但通过产品核心的许多代码路径没有经过良好测试)。【参考方案5】:

我对断言的使用遵循design by contract 的想法。基本上,您断言传入参数、全局变量和其他状态信息在您进入函数时是有效的,并且断言返回值和状态在您离开时也是有效的。如果你在开始的时候得到一个失败的断言,那是调用代码中的一个错误,如果你在最后得到它,那么这个错误就在这个代码中。这些是前置条件和后置条件。

switch 语句中的断言只有在您检查了进入的前提条件时才真正有用。在这种情况下,如果您在函数的中间到达不可接受的状态,则它是您的函数失败或被调用的函数。就个人而言,我会在这里坚持断言,因为它也不例外。正如您所暗示的,异常与资源有关,而不是错误。但是,您可以创建一个存在于发布版本中的自定义断言处理程序,并在失败时引发异常,让您的程序有机会返回到稳定状态。

【讨论】:

即使 MyClass 被打包并作为库发布给其他开发人员,也坚持使用断言? 是的。如果 MyClass 处于意外状态,则可能是内部错误或误用(不满足先决条件)。我不会为其中任何一个抛出异常。对于前者,我会返回一个错误的参数错误。对于后者,我在发布诊断模式中包含断言。【参考方案6】:

我想说:如果错误在其他人的代码中,或者在不同子系统的代码中(无论您是否编写),请使用异常。如果来自外部源(例如文件)的数据存在错误,也可以使用异常。

有时,您不知道数据是否来自外部来源。如果安全性很重要,并且您可能正在处理未经验证正确的外部数据,请使用异常。如果安全性不重要,那么我会将性能用作决胜局:此代码是否可以在紧密循环中执行?如果是这样,请考虑使用断言,因为您将在发布版本中获得更好的性能。否则,使用异常。

【讨论】:

【参考方案7】:

这取决于语言,如果您使用语法糖,则断言,那么您应该使用它。但是在 Java 中,需要打开断言才能使其工作,所以异常更好。但是有特定的异常总是更好,所以这里应该是 IllegalStateException。

【讨论】:

【参考方案8】:

在 .Net 中,当您进行发布构建时,您的程序中不包含 Debug 类方法,因此如果此代码用于生产,您将不得不抛出异常。

【讨论】:

以上是关于Debug.Assert 与异常的主要内容,如果未能解决你的问题,请参考以下文章

单元测试是不是使 Debug.Assert() 变得不必要?

C#:Release-Build-DL​​L 中的 Debug.Assert() 似乎由 Debug-Build-Project 激活

你好声称和断言这个两个词有何区别?

C# DEBUG 调试信息打印及输出详解

如何使用 Fluent Assertions 引发异常?

文件下载(适用于各个浏览器)