我在C中编写一个函数。作为一种风格问题,与返回错误代码相比,何时使用assert更好。让我们说这个功能正在划分两个数字。我应该断言除数是非零还是应该返回错误代码? 如果可以,请提供更多示例,以区分清楚。
答案 0 :(得分:38)
assert
中止了这个过程,但是当用-DNDEBUG
编译程序时,它变成了一个no-op,所以它是一个相当粗略的调试工具,仅此而已。您应该只使用assert
来检查“不可能发生”的情况,例如这违反了算法的不变量或后置条件,但可能不用于输入验证(当然不在库中)。当检测到来自客户端的无效输入时,请友好并返回错误代码。
assert
的示例用法可能是:您已经实现了一种非常智能的排序算法,并且您想检查它是否真的排序。由于排序函数应该“正常工作”,因此不返回值,因此无法在不更改API的情况下添加错误返回。
void sort(int *a, size_t n)
{
recursive_super_duper_sort(a, 0, n);
assert(is_sorted(a, n));
}
static bool is_sorted(int const *a, size_t n)
{
for (size_t i=0; i<n-1; i++)
if (a[i] > a[i+1])
return false;
return true;
}
从长远来看,你真的需要一个适当的单元测试框架而不是assert
,但它作为临时调试工具很有用。
答案 1 :(得分:17)
错误代码表示运行时行为。断言是一种调试工具,允许开发人员声明他们关于程序逻辑的假设确实是真的。
它们是两个完全不同的东西,有不同的应用程序。
错误代码是正常程序流程的一部分。断言仅用于调试,如果触发断言,则表示您的程序编写不正确。
答案 2 :(得分:14)
通常,断言是程序员(即您)在将程序发布给真实用户之前查找逻辑/编程错误。断言不应该用于检测运行时输入错误 - 使用错误代码。
答案 3 :(得分:8)
这真的是品味问题。这是我的意见。
主要经验法则:断言失败总是程序中的错误。
如果您希望调用者确保参数正确并且您想要指示任何其他行为是调用者中的错误,请使用assert
检查函数参数。除以零是IMO的一个很好的例子。
如果您希望调用者在调用之前无法确保参数正确,请使用错误代码。例如,事先检查参数可能在计算上非常昂贵。
从不使用assert
检查用户输入。
答案 4 :(得分:8)
传统观念是使用assert()来帮助调试代码,以便在发生“不可能”的事情时发出警告。这个“警告”采取退出程序的形式。
我听说Jim Coplien(一般的C ++大师和SCRUM培训师)主张让你的断言在部署的代码中保持活跃状态。 (听起来很疯狂,我知道......)这是专门针对高可靠性服务器代码的。动机是,最好是失败,努力,让另一个节点接管,而不是容忍你的服务器处于“不可能”的状态。
(当然,跟踪失败并分析它们。这意味着存在错误或不正确的假设。)
答案 5 :(得分:4)
首先,可以禁用assert
标头中的<assert.h>
(例如,通过使用gcc -DNDEBUG
进行编译),有时会禁用二进制文件的“生产”版本。
其次,正如Linux手册页所述,
The purpose of this macro is to help the programmer find bugs in his
program. The message "assertion failed in file foo.c, function
do_bar(), line 1287" is of no help at all to a user.
因此断言应该只在有缺陷的情况下失败。在特殊情况或错误情况下,您应该做其他事情。
某些工具(甚至编译器)可能会使用assert
- 离子,例如优化您的代码。
在quotient
函数的示例中,如果在整个程序中,你确定除数应该是非零的,那么你将使用assert
(但是它可能有意义命名函数的方式可能不同,可能是quotient_by_non_zero
)。如果您认为它可能发生,请将其作为致命消息,例外(即C中的longjmp
),错误代码等。
答案 6 :(得分:3)
由于C不支持异常,除了返回错误代码之外,您没有任何实际选项。失败的C assert()
会导致abort()
被调用,从而导致进程受到轰炸。这与标准错误处理无法真正比较。
对于浮点运算,您可以使用NaN
来指示错误情况。对于整数运算,错误代码只是您唯一的选择。
答案 7 :(得分:2)
当程序遇到不允许继续的情况时使用断言。断言是一个合同&#39;我把它们用作合同&#39;操作系统和情况以及延迟的危险&#39;。
为了模拟异常,你仍然可以使用GOTO&#39; ERRORLABEL&#39;运行清理功能后终止干净。
答案 8 :(得分:0)
这是我昨天写的一个断言的真实示例。
我有两个并行数组-分别称为a
和b
-我将要运行从0到{{1}大小的索引i
},然后对a
和a[i]
进行操作。但是,如果b[i]
中的元素少于b
中的元素,我的代码将在a
上违反数组边界。但是代码的其他部分是假定,以保持b
和a
的大小相同。因此,我在循环之前放置了一个断言,断言b
和a
的大小相等。
大小应该相等-但是,如果某种程度上大小不相等,我希望代码在告诉我原因的断言上失败,而不是在导致结果未定义的行为上奇怪地失败当我尝试读取数组b
的末尾(如果较小)时。
答案 9 :(得分:0)
assert
的本质是捕捉程序员认为不应该发生但可能发生的错误
让我们看看它的实际效果以及它为什么对我们有帮助。
我们设计了一个带有控制语句的函数来测试我们的动物是狗还是猫。我们的代码只处理狗和猫,作为程序员,我们相信 else 语句永远不会执行。但是它可能在我们的设计中由于未知错误(意外的初始化和赋值、未检测到的溢出等)而执行。
void isDogOrCat(T_animal x){
if(isDog(x)){
printf("Woof\n");
}
else if(isCat(x)){
printf("Meow\n");
}
else{
assert(false);
}
}
如果发生错误,我们会立即指向失败的断言,我们随后的调试尝试变得轻而易举。将此与仅拥有相比:
if(isDog(x)){
printf("Woof\n");
}
else{
printf("Meow\n");
}
如果一个错误在这里滑落,你就会遇到更大的麻烦。由于输出,您会认为您正在传递一只猫,但您不知道,您正在处理一个错误地超出您设计范围的变量!
(可以添加 else if(isCat(x))
而不是 else
,但在这种情况下,两个语句都不会执行,函数也不会输出任何内容 - 再次违反我们的设计)