17

令人惊讶的是,我只能找到关于这个主题的一个关于 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 问题中注意到了这个响应(使用断言或异常进行合同设计?):

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

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

欢迎提出想法!

4

8 回答 8

5

首先,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无效。这将向用户发送明确的信息,从而解决您的问题。

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

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

于 2009-02-03T08:20:31.350 回答
5

我基本上同意您自己的问题的结论:如果A lice的代码检测到 A lice 犯的错误则这是 A ssert 的情况除非性能另有规定,否则断言应保留在生产代码中)。如果 Alice 的代码在 Eve 的代码中检测到错误那么这是一个例外情况,假设 Alice 和 Eve 在的错误跟踪软件的对立面

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

于 2009-02-03T09:39:34.077 回答
5

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

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

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

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

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

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

于 2009-02-03T10:33:48.663 回答
4

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

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

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

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

于 2009-02-03T08:24:01.013 回答
2

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

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

于 2009-02-03T08:16:36.007 回答
1

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

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

于 2009-11-06T19:06:45.940 回答
0

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

于 2009-02-03T08:11:35.157 回答
0

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

于 2009-02-03T08:16:29.330 回答