【问题标题】:Design by contract and assert statements按合同设计和断言声明
【发布时间】:2012-12-11 13:37:50
【问题描述】:

我对@9​​87654321@ 方法很感兴趣。对于preconditions,似乎必须使用已检查的异常来强制执行它们。
但是对于post-conditionsclass-invariants,我认为assertions 是首选。
我对吗?如果我是正确的,为什么允许 post-conditionsclass-invariants 可能被禁用的断言?不应该也强制执行后置条件和不变量吗?

【问题讨论】:

  • 一个失败的后置条件表明在开发过程中会捕获一个编程错误,当断言被启用时——这是我唯一能想到的。使用其中一种似乎有些武断。无论如何,我更有可能使用方面。
  • @DaveNewton:方面不会有性能损失吗?

标签: java assert design-by-contract post-conditions


【解决方案1】:

组件上的后置条件和类不变量只有在组件本身编写不正确时才会失败。单元测试应该捕获所有这些。当然,在生产中实际检查它们是允许的,但这并不一定值得牺牲性能。

另一方面,如果该组件的用户不正确,前置条件可能会失败。对组件本身的测试无法检查这些,因此有必要更积极地失败,以便 那些 单元测试失败。

【讨论】:

  • 但是调用方法和参数,对类不变量没有副作用?那么为什么它们的处理方式与前置条件不同呢?
  • 如果您的代码允许类不变量在任何情况下都失败,那么这就是您的代码中的错误。如果您的代码被调用时输入无效,那是调用者代码中的错误。
  • On the other hand, preconditions can fail if the users of that component are correct. 你不是说不正确吗?
【解决方案2】:

根据定义,违反先决条件是编程错误。因此,最不幸的是用检查异常来表示这种违规行为。因为正确的代码将被强制显式捕获一个肯定不会抛出的异常,并将其作为未经检查的异常重新抛出,以便检测到编程错误。

【讨论】:

    【解决方案3】:

    你不应该区别对待它们——它们都应该是断言。

    OP 说:

    ... 似乎必须使用已检查的先决条件来强制执行它们。 ...为什么允许可能被禁用的后置条件和类不变量断言?不应该也强制执行后置条件和不变量吗?

    您似乎建议前置条件、后置条件和类不变量应该始终打开并始终由服务(方法/被调用者)检查。如果我们谈论的是源自 Bertrand Meyer 的 Design-by-Contract (DBC),那么情况并非如此。 Meyers 认为,从生产代码的角度来看,这些条件应该只在一个地方由客户端(调用者)或服务(被调用者)确保——这是客户端和服务之间的合同。相比之下,防御性编程说您应该在两个地方都对检查进行编码(Meyers 认为这很浪费并增加了不必要的复杂性)。

    DBC的合同部分是规范将明确谁负责什么:如果客户端将确保前置条件(并且服务可以假设它们为真),那么服务将确保后置条件(并且调用者可以假设它们是真实的)。迈耶斯当然明白,服务为了测试和调试目的检查前置条件/​​后置条件/不变量是明智的(以确保系统在测试/调试期间将fail fast),这就是为什么断言这些是明智的条件并在测试/调试期间启用断言,但目的是可以禁用或删除这些检查以进行生产。

    例如,如果您设计了一个堆栈,以便调用者有责任在调用 pop() 之前检查堆栈是否为空(前提条件),那么您也不应该在 pop() 方法中编写代码作为生产代码的一部分,检查堆栈是否为空,也不应该将检查的异常作为处理条件的方法签名的一部分。在那里有前置条件断言来帮助验证和调试是可以的,但目的是一旦开发和测试完成(代码可能没有错误),生产代码将只在调用者确保前置条件的情况下运行,而不是被调用者(如果这是您为 API 设计合约的方式)。

    如果您在 pop() 方法中有一个已检查的异常,并且您在 pop() 方法中检查堆栈是否为空作为永远在线的一部分,必须处理,生产代码,那么您是说服务(被调用者)负责检查,并且如果堆栈为空,它承诺抛出异常 - 它现在是后置条件的一部分。在这种情况下,调用者不需要明确地检查它——他们应该只处理异常。否则,如果调用者和被调用者都先检查堆栈是否为空,则不是合同设计。

    最后一点,有些人认为这意味着 Meyer 说调用者应始终负责检查堆栈是否为空或参数是否为空等。并非如此 - Meyers 只说您必须是在规范中明确前置条件和后置条件,以便正确编码客户端和服务。作为 API 设计者和实施者,您决定前置条件和后置条件中的内容。如果您可以合理地确定客户端(调用者)将确保前提条件(例如,因为它是一个内部类,并且您可以控制调用服务/方法的任何地方),那么将“堆栈非空”或“ parameter-a-is-not-null”部分的前提条件,并将责任放在客户端。但是,如果您认为这不是一个合理的假设(例如,它是一个公共 API 或无法审查或测试客户端以确保合规性)并且失败的先决条件的后果很严重,那么不要将这些假设作为一部分前提条件:继续将检查放入服务中,并将检查的异常作为签名的一部分 - 使其成为后置条件的一部分,即如果调用 pop() 时堆栈为空,则服务将抛出检查的异常例外。

    [[对于类似咆哮的回答,我很抱歉——我是 Bertrand Meyer 和 Design-by-Contract 的忠实粉丝。]]

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-09
      • 1970-01-01
      相关资源
      最近更新 更多