我对Design by Contract方法很感兴趣,似乎对于preconditions,必须使用检查异常来强制执行它们。但是对于post-conditions和class-invariants,我认为assertions是首选。我说的对吗?如果我说的对,为什么post-conditions和class-invariants可以禁用的Assert被允许?后置条件和不变量不也应该被强制吗?
Design by Contract
preconditions
post-conditions
class-invariants
assertions
iszxjhcz1#
组件上的后置条件和类不变量只有在组件本身编写不正确时才会失败。单元测试应该捕获所有这些。当然,在生产中实际检查它们是允许的,但这不一定值得牺牲性能。另一方面,如果组件的 users 不正确,前置条件可能会失败,组件本身的测试无法检查这些,所以必须更主动地失败,这样 * 那些 * 单元测试才会失败。
2hh7jdfx2#
违反前提条件是一个编程错误。因此,用检查异常来表示这样的违反是非常不幸的。因为正确的代码将被迫显式地捕捉一个肯定永远不会抛出的异常,并将其作为一个未检查异常重新抛出,以便可以检测到编程错误。
eimct9ow3#
您不应该区别对待它们-它们都应该是Assert。
OP说:...似乎对于检查过的前提条件,必须使用异常来强制执行它们。...为什么对于后置条件和类不变量,允许可以禁用的Assert?后置条件和不变量不也应该强制执行吗?您似乎建议前置条件、后置条件和类不变量应该始终处于打开状态,并始终由服务进行检查(方法/被调用方)。如果我们讨论Design-by-Contract(DBC),那么就不是这样了。Meyer认为,从生产代码的Angular 来看,这些条件应该只在一个地方得到保证,由客户端(调用者)或由服务(被调用者)执行--这是客户端和服务之间的契约。相反,防御性编程认为应该在这两个地方都编写检查代码(Meyer认为这是浪费,并增加了不必要的复杂性)。DBC的合同部分是规范将明确谁负责什么:如果客户将确保前提条件(并且服务可以假设它们将为真),则服务将确保后条件(调用者可以假设它们为真)Meyer当然明白,出于测试和调试的目的,服务检查前置条件/后置条件/不变量是明智的(以确保系统在测试/调试期间[fail fast] 2),这就是为什么在测试/调试期间Assert这些条件并启用Assert是明智的,但目的是为了在生产中禁用或删除这些检查。例如,如果您设计了一个堆栈,使得调用方有责任在调用pop之前检查堆栈是否为空()(前提条件),那么你就不应该在弹出菜单中编码()方法作为生产代码的一部分检查堆栈不为空,也不应将已检查异常作为方法签名的一部分来处理条件。它'在那里使用前置条件Assert来帮助验证和调试是可以的,但其意图是,一旦开发和测试完成(代码可能没有bug),产品代码将在只有调用者确保前置条件的情况下运行,而不是在被调用者(如果您是这样设计API契约的)的情况下运行。如果弹出过程中包含已检查异常()方法,并检查堆栈在弹出操作中是否为空()方法作为始终开启的一部分,必须进行处理,生产代码,那么您所说的服务(被调用方)负责检查,并且它承诺如果堆栈为空则抛出异常-它现在是后置条件的一部分。在这种情况下,调用者不需要显式地检查它--他们只需要处理异常。否则,如果调用者和被调用者都先检查堆栈是否为空,那么它就不是契约式设计。最后一点,有些人认为这意味着Meyer说调用者应该始终负责检查堆栈是否不为空或者参数是否不为空,Meyer只是说你必须清楚规范中的前置条件和后置条件,这样客户端和服务才能被正确地编码。的前置条件和后置条件。如果你能合理地确定客户(来电者)将确保前提条件(例如,因为它是一个内部类,您可以控制调用服务/方法的任何地方),然后将"stack-is-non-empty"或"parameter-a-is-not-null"作为前提条件的一部分,并将责任放在客户端。如果您认为这不是一个合理的假设(例如,它是一个公共API,或者无法对客户端进行审查或测试以确保合规性),并且未通过的前提条件的后果非常严重,则不要将这些假设作为前提条件的一部分:继续并将检查放入服务中,并使被检查的异常成为签名的一部分-使其成为后置条件的一部分,即如果在调用pop()时堆栈为空,则服务将抛出被检查的异常。抱歉我的回答像是咆哮--我是伯特兰·迈耶和契约式设计的忠实粉丝。
3条答案
按热度按时间iszxjhcz1#
组件上的后置条件和类不变量只有在组件本身编写不正确时才会失败。单元测试应该捕获所有这些。当然,在生产中实际检查它们是允许的,但这不一定值得牺牲性能。
另一方面,如果组件的 users 不正确,前置条件可能会失败,组件本身的测试无法检查这些,所以必须更主动地失败,这样 * 那些 * 单元测试才会失败。
2hh7jdfx2#
违反前提条件是一个编程错误。因此,用检查异常来表示这样的违反是非常不幸的。因为正确的代码将被迫显式地捕捉一个肯定永远不会抛出的异常,并将其作为一个未检查异常重新抛出,以便可以检测到编程错误。
eimct9ow3#
您不应该区别对待它们-它们都应该是Assert。
OP说:
...似乎对于检查过的前提条件,必须使用异常来强制执行它们。...为什么对于后置条件和类不变量,允许可以禁用的Assert?后置条件和不变量不也应该强制执行吗?
您似乎建议前置条件、后置条件和类不变量应该始终处于打开状态,并始终由服务进行检查(方法/被调用方)。如果我们讨论Design-by-Contract(DBC),那么就不是这样了。Meyer认为,从生产代码的Angular 来看,这些条件应该只在一个地方得到保证,由客户端(调用者)或由服务(被调用者)执行--这是客户端和服务之间的契约。相反,防御性编程认为应该在这两个地方都编写检查代码(Meyer认为这是浪费,并增加了不必要的复杂性)。
DBC的合同部分是规范将明确谁负责什么:如果客户将确保前提条件(并且服务可以假设它们将为真),则服务将确保后条件(调用者可以假设它们为真)Meyer当然明白,出于测试和调试的目的,服务检查前置条件/后置条件/不变量是明智的(以确保系统在测试/调试期间[fail fast] 2),这就是为什么在测试/调试期间Assert这些条件并启用Assert是明智的,但目的是为了在生产中禁用或删除这些检查。
例如,如果您设计了一个堆栈,使得调用方有责任在调用pop之前检查堆栈是否为空()(前提条件),那么你就不应该在弹出菜单中编码()方法作为生产代码的一部分检查堆栈不为空,也不应将已检查异常作为方法签名的一部分来处理条件。它'在那里使用前置条件Assert来帮助验证和调试是可以的,但其意图是,一旦开发和测试完成(代码可能没有bug),产品代码将在只有调用者确保前置条件的情况下运行,而不是在被调用者(如果您是这样设计API契约的)的情况下运行。
如果弹出过程中包含已检查异常()方法,并检查堆栈在弹出操作中是否为空()方法作为始终开启的一部分,必须进行处理,生产代码,那么您所说的服务(被调用方)负责检查,并且它承诺如果堆栈为空则抛出异常-它现在是后置条件的一部分。在这种情况下,调用者不需要显式地检查它--他们只需要处理异常。否则,如果调用者和被调用者都先检查堆栈是否为空,那么它就不是契约式设计。
最后一点,有些人认为这意味着Meyer说调用者应该始终负责检查堆栈是否不为空或者参数是否不为空,Meyer只是说你必须清楚规范中的前置条件和后置条件,这样客户端和服务才能被正确地编码。的前置条件和后置条件。如果你能合理地确定客户(来电者)将确保前提条件(例如,因为它是一个内部类,您可以控制调用服务/方法的任何地方),然后将"stack-is-non-empty"或"parameter-a-is-not-null"作为前提条件的一部分,并将责任放在客户端。如果您认为这不是一个合理的假设(例如,它是一个公共API,或者无法对客户端进行审查或测试以确保合规性),并且未通过的前提条件的后果非常严重,则不要将这些假设作为前提条件的一部分:继续并将检查放入服务中,并使被检查的异常成为签名的一部分-使其成为后置条件的一部分,即如果在调用pop()时堆栈为空,则服务将抛出被检查的异常。
抱歉我的回答像是咆哮--我是伯特兰·迈耶和契约式设计的忠实粉丝。