Debug.Assert vs Exceptions

时间:2009-02-03 07:56:29

标签: .net exception assertions

令人惊讶的是,我之前只能找到一个关于这个主题的关于SO的问题,而我只想让社区“投票自信”(或不是!)就我的方法而言。

我认为这样的方式是:

  • 使用Debug.Assert声明您的EXPECT将是真实的。当我们完全控制我们的环境时,例如在方法中,可以使用这个 验证一些前后条件。
  • 在出现特殊情况时使用例外情况。处理外部资源,即文件,数据库,网络等是不言而喻的。但是...

在以下场景中有点模糊。请注意,这是一个仅供参考的示例!

假设我们有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函数的前提条件,以及何时获得系统外部的任何数据。您应该将断言用于系统内部的函数或数据。

对我来说,这是有道理的,并且可以与下面概述的“Assert then throw”技术相结合。

欢迎思考!

8 个答案:

答案 0 :(得分:5)

首先,MyClass有效当然应该由MyClass的不变表示。

其次,你说“我们希望MyMode能够被这个类的外部用户更新” - 当然这个模式的setter应该具有典型的按合同设计形式(作为任何公共函数):

  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 无效。这会向用户发送一条明确的消息,从而解决您的问题。

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

编辑:关于断言和发布模式,另请参阅:

答案 1 :(得分:5)

我基本同意你自己的问题的结论:如果 A 虱子的代码检测到 A 虱子的错误,那么 A ssert(断言应该在生产代码中保留,除非性能另有规定)。如果Alice的代码检测到 E ve代码中的错误,那么 E xceptions就是一种情况,假设Alice和Eve位于错误跟踪软件的两侧

现在这是一般的经验法则。断言,稍微修改一下,也可以用作“抬头,开发人员!”机制(然后它们不应该被称为“ASSERT”,而是“HEADS_UP”或类似的东西)。如果您的公司开发客户端/服务器产品,并且服务器向客户端发送无效数据,该怎么办?如果您是客户端程序员,您会觉得将其视为外部数据(这是Eve的数据是kaputt),并且您想要抛出异常。但是,一个“更软”的断言,使Visual Studio的调试器停在那里,可以很早地检测到这些问题并将其报告给服务器团队。在一个真正的装置中,很可能是Mallory使用Eve和Alice之间的数据进行调整,但大部分时间它都是你的同事之一的错误,并且你想在它发生的时候看到它 - 这就是我称它们为什么的原因“单挑”断言:它们不会取代异常,但它们会给你一个警告并有机会检查问题。

答案 2 :(得分:5)

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

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

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

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

所以你原来的帖子中的问题......“我在这里得到的是MyClass的用户将其置于无效状态。那么我们该怎么办?”......永远不会出现。< / p>

您可能永远不需要再次调试任何东西! ; - )

答案 3 :(得分:4)

经常两者:断言,然后扔。

你坚持,因为你想在开发过程中通知开发人员错误的假设。

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

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

答案 4 :(得分:2)

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

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

答案 5 :(得分:1)

我会说:如果错误在别人的代码中,或在不同子系统的代码中(无论你是否写过),都要使用异常。如果来自外部源(如文件)的数据出错,也请使用异常。

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

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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