Go语言的创造者写道:
Go doesn't provide assertions. They are undeniably convenient, but our experience has been that programmers use them as a crutch to avoid thinking about proper error handling and reporting. Proper error handling means that servers continue operation after non-fatal errors instead of crashing. Proper error reporting means that errors are direct and to the point, saving the programmer from interpreting a large crash trace. Precise errors are particularly important when the programmer seeing the errors is not familiar with the code.
你对此有什么看法?
断言并不邪恶,但很容易被滥用。我非常同意“断言经常被用作一种拐杖,以避免考虑正确的错误处理和报告”的说法。我经常看到这种情况。
就我个人而言,我确实喜欢使用断言,因为它们记录了我在编写代码时可能做出的假设。如果在维护代码时这些假设被打破了,那么可以在测试期间检测到问题。但是,在进行产品构建时(即使用#ifdefs),我确实强调要从代码中剥离出每个断言。通过剥离产品构建中的断言,我消除了任何人将其误用为拐杖的风险。
断言还有另一个问题。断言只在运行时进行检查。但是通常情况下,您想要执行的检查可以在编译时执行。最好在编译时检测问题。对于c++程序员,boost提供了BOOST_STATIC_ASSERT,它允许您执行此操作。对于C程序员,本文(链接文本)描述了一种可用于在编译时执行断言的技术。
总之,我遵循的经验法则是:不要在生产构建中使用断言,如果可能的话,只对在编译时无法验证的东西使用断言(即,必须在运行时检查)。
我更倾向于避免在调试和发布中做不同事情的代码。
但是,在一个条件下中断调试器并获得所有文件/行信息,以及确切的表达式和确切的值是有用的。
拥有一个“只在调试中评估条件”的断言可能是一种性能优化,因此,它只在0.0001%的程序中有用——人们知道他们在做什么。在所有其他情况下,这是有害的,因为表达式实际上可能改变程序的状态:
assert(2 == shroedingerscat . getnummears ());
会使程序在调试和发布时做不同的事情。
我们开发了一组assert宏,可以在调试版和发布版中抛出异常。例如,THROW_UNLESS_EQ(a, 20);what()消息同时包含文件、行和a的实际值,等等,将抛出异常。只有宏才有这个功能。调试器可以配置为在特定异常类型的'throw'时中断。
Assert非常有用,可以在出现意外错误时通过在出现问题的第一个迹象时停止程序来节省大量回溯时间。
另一方面,断言很容易被滥用。
int quotient(int a, int b){
assert(b != 0);
return a / b;
}
正确的说法应该是:
bool quotient(int a, int b, int &result){
if(b == 0)
return false;
result = a / b;
return true;
}
所以…从长远来看……从大局来看……我必须同意断言可能会被滥用。我一直都这么做。
这种情况经常出现,我认为让断言辩护令人困惑的一个问题是,它们通常基于参数检查。所以考虑一下这个不同的例子,当你使用断言时:
build-sorted-list-from-user-input(input)
throw-exception-if-bad-input(input)
...
//build list using algorithm that you expect to give a sorted list
...
assert(is-sorted(list))
end
您对输入使用异常是因为您预计有时会得到错误的输入。您断言对列表进行排序是为了帮助您找到算法中的bug,根据定义,这是您所不期望的。断言只存在于调试版本中,因此即使检查的开销很大,您也不介意对例程的每次调用都进行检查。
您仍然需要对产品代码进行单元测试,但这是确保代码正确的一种不同的补充方式。单元测试确保您的例程符合其接口,而断言是一种更细粒度的方法,以确保您的实现完全按照您的期望进行。