通过合同和断言声明进行设计

时间:2012-12-25 18:31:45

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

我对Design by Contract方法感兴趣。似乎必须使用preconditions已检查的例外来强制执行它们 但对于post-conditionsclass-invariants,我认为assertions是首选 我对吗?如果我是正确的,为什么post-conditionsclass-invariants允许可以禁用的断言?不应该强制执行后置条件和不变量吗?

3 个答案:

答案 0 :(得分:8)

组件上的发布条件和类不变量只有在组件本身写错时才会失败。单元测试应该能够捕获所有这些。当然,允许在生产中实际检查它们,但这不一定值得性能权衡。

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

答案 1 :(得分:3)

违反前提条件的定义是编程错误。因此,最不幸的是用经过检查的例外来表明这种违规行为。因为正确的代码将被强制显式捕获当然永远不会被抛出的异常,并将其重新抛出为未经检查的异常,以便可以检测到编程错误。

答案 2 :(得分:0)

你不应该以不同的方式对待它们 - 它们都应该是断言。

OP说:

  

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

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

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

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

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

最后请注意,有些人认为这意味着Meyer's说调用者应该总是负责检查堆栈是否为空或参数不为空等等。不是这样 - Meyers只说您必须在规范中明确前提条件和后置条件,以便可以正确编码客户端和服务。您作为API设计者和实现者决定前提条件和后置条件中的内容。如果您可以合理地确定客户端(调用者)将确保前提条件(例如,因为它是一个内部类,并且您控制调用服务/方法的所有位置),那么使" stack-is-not-empty&# 34;或者"参数-a-is-not-null"部分前提并将责任放在客户端上。但是,如果您认为这不是一个合理的假设(例如,它的公共API或客户无法进行审核或测试以确保合规性),并且失败的前提条件的后果严重,那么请不要这样做。使这些假设成为前提条件的一部分:继续并将检查放入服务中并使签名的异常成为签名的一部分 - 使其成为后置条件的一部分,如果在调用pop()时堆栈为空,则该服务将抛出已检查的异常。

[[很抱歉这个咆哮的答案 - 我是Bertrand Meyer和设计合同的忠实粉丝。]]

相关问题