当程合同的一个函数或方法的第一个检查是否是其先决条件得到满足,在开始工作,其责任,对吗?两个最突出的方式做这些检查都是由 assert 而通过 exception.

  1. 主张失败,只有在调试模式。确保它是至关重要(单位)测试所有的单独合同的先决条件,看看他们是否实际上失败。
  2. 异常失败,在调试和发布方式。这具有的好处,测试行为是相同的释放行为,但它需支付运行性能的惩罚。

哪一个你认为是优选的?

见相关型号的问题 在这里,

有帮助吗?

解决方案

在发布版本中禁用断言就像是说“我将永远不会在发布版本中出现任何问题”,这通常不是这种情况。因此,不应在发布版本中禁用assert。但是,您不希望发生错误时发布版本崩溃,是吗?

因此,请使用异常并充分利用它们。使用一个好的,可靠的异常层次结构并确保捕获并且可以在调试器中抛出异常抛出钩子以捕获它,并且在释放模式下,您可以补偿错误而不是直接崩溃。这是更安全的方式。

其他提示

经验法则是,当您尝试捕获自己的错误时应使用断言,并在尝试捕获其他人的错误时使用异常。换句话说,您应该使用异常来检查公共API函数的前提条件,以及何时获得系统外部的任何数据。您应该将断言用于系统内部的函数或数据。

我遵循的原则是:如果通过编码可以现实地避免某种情况,那么使用断言。否则使用例外。

断言是为了确保遵守合同。合同必须公平,以便客户必须能够确保其符合要求。例如,您可以在合同中声明URL必须有效,因为关于什么是有效URL的规则是已知且一致的。

例外情况适用于客户端和服务器无法控制的情况。一个例外意味着某些事情出了问题,并且没有任何事情可以避免它。例如,网络连接在应用程序控制之外,因此无法避免网络错误。

我想补充一点,Assertion / Exception区别并不是考虑它的最佳方式。您真正想要考虑的是合同以及如何实施合同。在我上面的URL示例中,最好的办法是使用一个封装URL的类,它是Null或有效的URL。它是将字符串转换为强制执行合同的URL,如果无效则抛出异常。带有URL参数的方法比具有String参数的方法和指定URL的断言要清晰得多。

断言是为了捕捉开发人员做错的事情(不仅仅是你自己 - 团队中的另一位开发人员)。如果用户错误可能会造成这种情况是合理的,那么它应该是一个例外。

同样考虑后果。断言通常会关闭应用程序。如果有任何现实的期望可以恢复条件,你应该使用例外。

另一方面,如果问题是由程序员错误引起的,那么请使用断言,因为您希望尽快了解它。可能会捕获并处理异常,您永远不会发现它。是的,您应该在发布代码中禁用断言,因为如果有可能的话,您希望应用程序恢复。即使你的程序状态被严重破坏,用户也可能能够保存他们的工作。

“断言仅在调试模式下失败”并不完全正确。“

在Bertrand Meyer的面向对象软件构建,第2版中,作者打开了一扇门,用于检查发布模式中的前提条件。在这种情况下,当断言失败时会发生什么......提出了断言违例异常!在这种情况下,情况无法恢复:虽然可以做一些有用的事情,但它会自动生成错误报告,并在某些情况下重新启动应用程序。

这背后的动机是,前提条件通常比不变量和后置条件更便宜,并且在某些情况下,正确性和“安全性”也是如此。在发布版本中比速度更重要。即对于许多应用程序而言,速度不是问题,但健壮性(程序在其行为不正确时,即在合同被破坏时以安全的方式行为的能力)是。

您是否应始终启用前置条件检查?这取决于。由你决定。没有普遍的答案。如果您正在为银行制作软件,最好使用警报消息中断执行,而不是转移1,000,000美元而不是1,000美元。但是如果你正在编程游戏呢?也许你需要你可以获得的所有速度,并且如果有人得到1000分而不是10分,因为前提条件没有捕获的错误(因为它们没有启用),运气不好。

在这两种情况下,理想情况下,您应该在测试期间捕获该错误,并且应该在启用断言的情况下执行大部分测试。这里讨论的是对于那些由于测试不完整而未在早期检测到的情况下生产代码中的前提条件失败的极少数情况的最佳策略。

总结一下,如果你让它们保持启用,你可以拥有断言并仍然自动获得例外 - 至少在Eiffel中。我想在C ++中也一样,你需要自己输入它。

另请参阅:断言何时应保留在生产代码中?

有一个巨大的关于在comp.lang.c ++。moderated中的发布版本中启用/禁用断言的线程,如果你有几个星期,你可以看到对此的看法有多么不同。 :)

coppro 相反,我相信如果你不确定在发布版本中是否可以禁用断言,那么它就不应该是一个断言。断言是为了防止程序不变量被破坏。在这种情况下,就您的代码的客户而言,将会有两种可能的结果之一:

  1. 因某种操作系统类型故障而死亡,导致调用中止。 (没有断言)
  2. 直接打电话到中止。 (有断言)
  3. 对用户没有任何区别,但是,断言可能会在代码中出现的代码中添加不必要的性能成本,而代码中的代码不会失败。

    问题的答案实际上更多地取决于API的客户端是谁。如果您正在编写提供API的库,那么您需要某种形式的机制来通知您的客户他们错误地使用了API。除非你提供两个版本的库(一个带有断言,一个没有断言),否则断言是不太可能的选择。

    然而,就个人而言,我不确定我是否会对这种情况采用例外情况。例外更适合可以进行适当形式的恢复的地方。例如,您可能正在尝试分配内存。当你捕获'std :: bad_alloc'异常时,可能会释放内存并重试。

我概述了我的观点对国家问题: 你如何验证对象的内部状态? .一般来说,维护你的权利要求和扔用于违反其他人。对禁用说,在发布版本,你能做到:

  • 禁止称为昂贵的检查(如检查是否有范围被命令)
  • 保持微不足道的检查,启用(如检查一空指针或一boolean value)

当然,在释放的生成、失败的断言和未捕获的例外情况应该处理的另一个办法比在调试版本(这里它可以只叫std::中止).写一本日志的错误的地方(可能进入一个文件),告诉客户,内部发生错误。客户将能够发送你登录文件。

您在询问设计时和运行时错误之间的区别。

断言是'嘿程序员,这是破坏'通知,他们在那里提醒你在发生错误时你不会注意到的错误。

例外是'嘿用户,有些事情出错'通知(显然你可以编写代码来捕获它们以便用户永远不会被告知)但这些是为Joe用户使用应用程序时的运行时设计的。

因此,如果您认为可以解决所有错误,请仅使用例外。如果你认为你不能......使用例外。您仍然可以使用调试断言来减少异常数量。

不要忘记许多前提条件都是用户提供的数据,因此您需要一种很好的方式来通知用户他的数据不好。为此,您通常需要将调用堆栈中的错误数据返回到与其交互的位。如果您的应用程序是n层,那么断言就没用了 - 加倍。

最后,我不使用 - 错误代码远远优于您认为会定期发生的错误。 :)

我更喜欢第二个。虽然您的测试可能运行良好,但 Murphy 表示会出现意外情况。因此,不是在实际的错误方法调用中获得异常,而是最终找出更深层次的NullPointerException(或等效的)10个堆栈帧。

之前的答案是正确的:对公共API函数使用异常。您可能希望弯曲此规则的唯一时间是检查计算成本高昂。在这种情况下,您可以将其置于断言中。

如果您认为可能存在违反该前提条件的情况,请将其作为例外处理,或者重构前提条件。

你应该同时使用两者。断言是为了方便您作为开发人员。异常会捕获您在运行时遗漏或未预料到的内容。

我喜欢 glib的错误报告功能而不是普通的旧断言。它们的行为类似于断言语句,但它们不是暂停程序,而是返回一个值并让程序继续运行。它的效果非常好,作为奖励,你可以看到当一个函数没有返回“它应该是什么”时,你的程序的其余部分会发生什么。如果它崩溃了,你知道你的错误检查在其他地方很麻烦。

在我的上一个项目中,我使用这些样式的函数来实现前置条件检查,如果其中一个失败,我会将堆栈跟踪打印到日志文件但继续运行。当其他人在运行我的调试版本时遇到问题时,节省了大量的调试时间。

#ifdef DEBUG
#define RETURN_IF_FAIL(expr)      do {                      \
 if (!(expr))                                           \
 {                                                      \
     fprintf(stderr,                                        \
        "file %s: line %d (%s): precondition `%s' failed.", \
        __FILE__,                                           \
        __LINE__,                                           \
        __PRETTY_FUNCTION__,                                \
        #expr);                                             \
     ::print_stack_trace(2);                                \
     return;                                                \
 };               } while(0)
#define RETURN_VAL_IF_FAIL(expr, val)  do {                         \
 if (!(expr))                                                   \
 {                                                              \
    fprintf(stderr,                                             \
        "file %s: line %d (%s): precondition `%s' failed.",     \
        __FILE__,                                               \
        __LINE__,                                               \
        __PRETTY_FUNCTION__,                                    \
        #expr);                                                 \
     ::print_stack_trace(2);                                    \
     return val;                                                \
 };               } while(0)
#else
#define RETURN_IF_FAIL(expr)
#define RETURN_VAL_IF_FAIL(expr, val)
#endif

如果我需要运行时检查参数,我会这样做:

char *doSomething(char *ptr)
{
    RETURN_VAL_IF_FAIL(ptr != NULL, NULL);  // same as assert(ptr != NULL), but returns NULL if it fails.
                                            // Goes away when debug off.

    if( ptr != NULL )
    {
       ...
    }

    return ptr;
}

我尝试用自己的观点综合其他几个答案。

对于你想在生产中禁用它的情况使用断言,错误地让它们进入。在生产中禁用而不是在开发中禁用的唯一真正原因是加速程序。在大多数情况下,这种加速速度并不显着,但有时代码是时间关键的,或者测试计算成本很高。如果代码是关键任务,那么尽管速度变慢,异常可能是最好的。

如果有任何真正的恢复机会,请使用例外,因为断言不是为了从中恢复的。例如,代码很少设计用于从编程错误中恢复,但它旨在从网络故障或锁定文件等因素中恢复。错误不应仅仅因为不受程序员控制而作为例外处理。相反,与编码错误相比,这些错误的可预测性使得它们更容易恢复。

重新论证调试断言更容易:来自正确命名的异常的堆栈跟踪与断言一样容易阅读。好的代码应该只捕获特定类型的异常,因此异常不应该因为被捕获而被忽视。但是,我认为Java有时会迫使您捕获所有异常。

另见这个问题

  

在某些情况下,构建发布时会断言断言。你可以   无法控制这个(否则,你可以用断言构建   on),所以这样做可能是个好主意。

     

“纠正”问题输入值是调用者的意思   没有得到他们所期望的,这可能导致问题,甚至   在程序的完全不同的部分崩溃,进行调试   梦魇。

     

我通常在if语句中抛出异常来接管角色   如果它们被禁用,则断言

assert(value>0);
if(value<=0) throw new ArgumentOutOfRangeException("value");
//do stuff

对我而言,经验法则是使用断言表达式来查找外部错误的内部错误和异常。 Greg从此处

  

Assert表达式用于查找编程错误:程序逻辑本身中的错误或其相应实现中的错误。断言条件验证程序是否保持定义状态。 “定义的状态”是指定义的状态。基本上是一个同意该计划的假设。注意,“定义的状态”是指“定义的状态”。对于程序而言,不一定是“理想状态”。或者甚至是“通常的状态”,或者甚至是“有用的状态”,或者甚至是“有用的状态”。但更重要的是稍后这一点。

     

要了解断言如何适合程序,请考虑一个例程   一个即将取消引用指针的C ++程序。现在应该   例程测试在解除引用之前指针是否为NULL,或者   它应该断言指针不是NULL然后继续前进   无论如何取消引用它?

     

我想大多数开发人员都希望同时执行这两项操作,添加断言,   还要检查指针是否有NULL值,以免崩溃   如果断言条件失败。表面上,执行两个   测试和检查似乎是最明智的决定

     

与其声明的条件不同,程序的错误处理(例外)不是指   程序中的错误,但程序从其获得的输入   环境。这些通常是“错误”。在某人身上,例如用户   试图在不输入密码的情况下登录帐户。和   即使错误可能会阻止程序的成功完成   任务,没有程序失败。程序无法登录用户   没有密码由于外部错误 - 用户的错误   部分。如果情况不同,并且用户输入了   密码正确,程序无法识别;然后虽然   结果仍然是相同的,失败现在属于   该计划。

     

错误处理(例外)的目的有两个。首先是沟通   程序输入中的错误给用户(或其他一些客户端)   被发现并意味着什么。第二个目标是恢复   检测到错误后的应用程序,到一个定义良好的状态。注意   在这种情况下程序本身没有错误。当然,   程序可能处于非理想状态,甚至处于可以执行的状态   没什么用,但没有编程错误。反之,   因为错误恢复状态是程序预期的状态   设计,它是程序可以处理的一个。

PS:您可能想查看类似的问题:异常与断言

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top