通过合同编程时,函数或方法首先检查其前提是否已满足,然后才开始履行其职责,对吗?两个最重要的方式做这些检查是通过assert
和exception
.
断言仅在调试模式下失败.确保(单元)测试所有单独的合同前提条件以确定它们是否确实失败是至关重要的.
在调试和释放模式下异常失败.这样做的好处是,测试的调试行为与发布行为相同,但它会导致运行时性能下降.
你觉得哪一个更好?
请参阅此处的相关问题
经验法则是,在尝试捕获自己的错误时应使用断言,在尝试捕获其他人的错误时应使用异常.换句话说,您应该使用异常来检查公共API函数的前提条件,以及何时获得系统外部的任何数据.您应该将断言用于系统内部的函数或数据.
在发布版本中禁用断言就像是说"我将永远不会在发布版本中出现任何问题",这通常不是这种情况.因此,不应在发布版本中禁用assert.但是,您不希望发生错误时发布版本崩溃,是吗?
所以使用异常并很好地使用它们.使用一个好的,可靠的异常层次结构并确保捕获并且可以在调试器中抛出异常抛出钩子以捕获它,并且在释放模式下,您可以补偿错误而不是直接崩溃.这是更安全的方式.
我遵循的原则是:如果通过编码可以现实地避免情况,那么使用断言.否则使用例外.
断言是为了确保遵守合同.合同必须公平,以便客户必须能够确保其符合要求.例如,您可以在合同中声明URL必须有效,因为关于什么是有效URL的规则是已知且一致的.
例外情况适用于客户端和服务器无法控制的情况.一个例外意味着某些事情出了问题,并且没有任何事情可以避免它.例如,网络连接在应用程序控制之外,因此无法避免网络错误.
我想补充一点,Assertion/Exception的区别并不是考虑它的最佳方式.您真正想要考虑的是合同以及如何实施合同.在我上面的URL示例中,最好的办法是使用一个封装URL的类,它是Null或有效的URL.它是将字符串转换为强制执行合同的URL,如果无效则抛出异常.具有URL参数的方法比具有String参数的方法和指定URL的断言更清晰.
断言是为了捕捉开发人员做错的事情(不仅仅是你自己 - 团队中的另一个开发人员).如果用户错误可以创建这个条件是合理的,那么它应该是一个例外.
同样考虑后果.断言通常会关闭应用程序.如果有任何现实的期望可以从中恢复条件,则应该使用异常.
另一方面,如果问题只能由程序员错误引起,那么请使用断言,因为您希望尽快了解它.可能会捕获并处理异常,您永远不会发现它.是的,您应该在发布代码中禁用断言,因为如果有可能的话,您希望应用程序恢复.即使您的程序状态被严重破坏,用户也可能能够保存他们的工作.
"断言仅在调试模式下失败"并不完全正确.
在Bertrand Meyer的第二版"面向对象软件构建"中,作者留下了一扇门,用于检查发布模式中的前提条件.在这种情况下,当断言失败时会发生什么......提出了断言违例异常!在这种情况下,情况无法恢复:虽然可以做一些有用的事情,但它会自动生成错误报告,在某些情况下,还可以重新启动应用程序.
这背后的动机是,前提条件通常比不变量和后置条件更便宜,并且在某些情况下,发布版本中的正确性和"安全性"比速度更重要.即对于许多应用程序而言,速度不是问题,而是稳健性(程序在其行为不正确时,即在合同被破坏时以安全的方式行为的能力).
您是否应始终启用前置条件检查?这取决于.由你决定.没有普遍的答案.如果您正在为银行制作软件,最好使用警报消息中断执行,而不是转移1,000,000美元而不是1,000美元.但是如果你正在编程游戏呢?也许你需要你可以获得的所有速度,如果有人得到1000分而不是10分,因为前提条件没有捕获的错误(因为它们没有启用),运气不好.
在这两种情况下,理想情况下,您应该在测试期间捕获该错误,并且应该在启用断言的情况下执行大部分测试.这里讨论的是对于那些由于不完整的测试而未在早期检测到的情况下生产代码中的前提条件失败的罕见情况的最佳策略是什么.
总而言之,如果你让它们保持启用状态,你可以拥有断言并自动获得异常 - 至少在Eiffel中.我想在C++中做同样的事情你需要自己输入它.
另请参见:断言何时应保留在生产代码中?