断言是邪恶的吗?

huangapple go评论98阅读模式
英文:

Is assert evil?

问题

“Go”语言的创建者们写道

>**Go不提供断言。**它们无疑很方便,但我们的经验是,程序员们将它们用作避免思考正确的错误处理和报告的支撑。正确的错误处理意味着服务器在非致命错误后继续运行,而不是崩溃。正确的错误报告意味着错误直接明了,节省了程序员解释大量崩溃跟踪的时间。当看到错误的程序员对代码不熟悉时,准确的错误尤为重要。

这些论点如何适用于C或C++?使用assert()的一些优缺点是什么?

英文:

The Go language creators write:

>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.

How would these arguments apply to C or C++? What are some pros and cons of using assert()?

答案1

得分: 329

不,只要按照预期使用,assert没有任何问题。

也就是说,它应该用于在调试过程中捕捉“不可能发生”的情况,而不是用于正常的错误处理。

  • 断言:程序逻辑本身的失败。
  • 错误处理:由于程序中的错误而导致的错误输入或系统状态。
英文:

No, there's nothing wrong with assert as long as you use it as intended.

That is, it's supposed to be for catching cases that "can't happen", during debugging, as opposed to normal error handling.

  • Assert: A failure in the program's logic itself.
  • Error Handling: An erroneous input or system state not due to a bug in the program.

答案2

得分: 112

不,gotoassert都不是邪恶的。但是两者都可能被滥用。

assert用于检查程序的正确性。如果不正确,它们应该终止程序。而不是用于验证或替代错误处理。

英文:

No, neither goto nor assert are evil. But both can be misused.

Assert is for sanity checks. Things that should kill the program if they are not correct. Not for validation or as a replacement for error handling.

答案3

得分: 62

按照这种逻辑,断点也是邪恶的。

断言应该被用作调试辅助工具,而不是其他用途。当你试图使用它们来代替错误处理时,才会是“邪恶”的。

断言的存在是为了帮助你,程序员,检测和修复不应该存在的问题,并验证你的假设是否正确。

它们与错误处理无关,但不幸的是,一些程序员滥用它们,并将它们宣称为“邪恶”。

英文:

By that logic, breakpoints are evil too.

Asserts should be used as a debugging aid, and nothing else. "Evil" is when you try using them instead of error handling.

Asserts are there to help you, the programmer, detect and fix problems that must not exist and verify that your assumptions stay true.

They have nothing to do with error handling, but unfortunately, some programmers abuse them as such, and then declare them "evil".

答案4

得分: 41

我喜欢经常使用断言。我发现在第一次构建应用程序时(也许是为了一个新的领域),它非常有用。与进行非常复杂的错误检查(我认为这是过早优化)相比,我会快速编码并添加很多断言。在我更了解事物如何工作之后,我会进行重写,删除一些断言,并将它们改为更好的错误处理方式。

由于断言,我在编码/调试程序上花费了更少的时间。

我还注意到,断言帮助我考虑到可能破坏我的程序的许多事情。

英文:

I like to use assert a lot. I find it very useful when I am building applications for the first time (perhaps for a new domain). Instead of doing very fancy error checking (that I would consider premature optimization) I code fast and I add a lot of asserts. After I know more about how things work I do a rewrite and remove some of the asserts and change them for better error handling.

Because of asserts I spend a lot of less time coding/debugging programs.

I've also noticed that the asserts help me think of many things that could break my programs.

答案5

得分: 31

作为额外的信息,Go语言提供了一个内置函数panic。它可以用来替代assert。例如:

if x < 0 {
    panic("x is less than 0");
}

panic会打印出堆栈跟踪,因此在某种程度上它具有assert的作用。

英文:

As an additional information, go provides a built-in function panic. This can be used in place of assert. E.g.

if x &lt; 0 {
    panic(&quot;x is less than 0&quot;);
}

panic will print the stack trace, so in some way it has the purpose of assert.

答案6

得分: 30

它们应该用于检测程序中的错误。不是坏的用户输入。

如果使用正确,它们不是邪恶的。

英文:

They should be used for detecting bugs in the program. Not bad user input.

If used correctly, they are not evil.

答案7

得分: 13

这种情况经常出现,我认为使对断言的辩护变得混乱的一个问题是它们通常基于参数检查。所以考虑以下不同的例子,说明何时可能使用断言:

从用户输入构建排序列表(input)

如果输入不良,则抛出异常(input)

...

//使用你期望能给出排序列表的算法构建列表

...

断言列表已排序,以帮助你找到算法中的错误,这是你不期望的。断言仅在调试构建中存在,因此即使检查是昂贵的,你也不介意在每次调用例程时执行它。

你仍然需要对生产代码进行单元测试,但这是一种不同且互补的方式,以确保你的代码是正确的。单元测试确保你的例程符合其接口,而断言是一种更细粒度的方式,确保你的实现正好按照你的期望执行。

英文:

This comes up a lot, and I think one problem that makes defenses of assertions confusing is that they are often based on argument checking. So consider this different example of when you might use an assertion:

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

You use an exception for the input because you expect you'll get bad input sometimes. You assert that the list is sorted to help you find a bug in your algorithm, which by definition you don't expect. The assertion is in the debug build only, so even though the check is expensive, you don't mind doing it on every single invocation of the routine.

You still have to unit-test your production code, but that's a different, and complementary, way of making sure your code is correct. Unit tests make sure your routine lives up to its interface, while assertions are a finer-grained way to make sure your implementation is doing exactly what you expect it to.

答案8

得分: 8

断言并不是邪恶的,但它们很容易被滥用。我同意这个说法:“断言经常被用作避免考虑正确的错误处理和报告的支撑”。我经常见到这种情况。

就个人而言,我喜欢使用断言,因为它们可以记录我在编写代码时可能做出的假设。如果在维护代码时这些假设被打破,问题可以在测试期间被检测出来。然而,我会在进行生产构建时(即使用#ifdefs)从我的代码中删除每个断言。通过在生产构建中删除断言,我消除了任何人滥用它们作为支撑的风险。

断言还有另一个问题。断言只在运行时进行检查。但通常情况下,您希望执行的检查可以在编译时进行。最好在编译时检测到问题。对于C++程序员,Boost提供了BOOST_STATIC_ASSERT,可以实现这一点。对于C程序员,这篇文章(链接:link text)描述了一种可以用于在编译时执行断言的技术。

总之,我遵循的经验法则是:不要在生产构建中使用断言,如果可能的话,只在无法在编译时验证的情况下使用断言(即必须在运行时检查的情况)。

英文:

Assertions are not evil but they can be easily misused. I do agree with the statement that "assertions are often used as a crutch to avoid thinking about proper error handling and reporting". I have seen this quite often.

Personally, I do like to use assertions because they document assumptions that I might have made whilst writing my code. If these assumptions are broken while maintaining the code, the problem can be detected during test. However, I do make the point of stripping out every assert from my code when doing a production build (i.e., using #ifdefs). By stripping out the assertions in the production build, I eliminate the risk of anyone misusing them as a crutch.

There is also another problem with assertions. Assertions are only checked at run-time. But it is often the case that the check you would like to perform could have been performed at compile-time. It is preferable to detect an issue at compile time. For C++ programmers, boost provides BOOST_STATIC_ASSERT which allows you to do this. For C programmers, this article ( link text ) describes a technique that can be used to perform assertions at compile time.

In summary, the rule of thumb I follow is: Do not use assertions in a production build and, if possible, only use assertions for things that cannot be verified at compile-time (i.e., must be checked at run-time).

答案9

得分: 6

我更喜欢避免在调试和发布版本中执行不同操作的代码。

在调试器中根据条件中断,并且拥有所有的文件/行信息是有用的,同时还有确切的表达式和确切的值。

有一个断言可以“仅在调试时评估条件”,可能是一种性能优化,因此仅在0.0001%的程序中有用 - 在这些程序中人们知道自己在做什么。在其他所有情况下,这是有害的,因为表达式实际上可能会改变程序的状态:

assert(2 == ShroedingersCat.GetNumEars());
会导致程序在调试和发布版本中执行不同的操作。

我们开发了一套断言宏,它们会抛出异常,并且在调试和发布版本中都会抛出异常。例如,THROW_UNLESS_EQ(a, 20); 会抛出一个带有what()消息的异常,其中包含文件、行和a的实际值等信息。只有宏才有这种能力。调试器可以配置为在特定异常类型的“抛出”处中断。

英文:

I prefer avoiding code that does different things in debug and release.

Breaking in the debugger on a condition and having all file/line info is useful though, also the exact expression and the exact value.

Having an assert that would "evaluate the condition only in debug" may be a performance optimization, and as such, useful only in 0.0001% of programs - where people know what they are doing. In all other cases this is harmful, as the expression may actually change program's state:

assert(2 == ShroedingersCat.GetNumEars());
would make the program do different things in debug and release.

We have developed a set of assert macros which would throw an exception, and do it in both debug and release version. For instance, THROW_UNLESS_EQ(a, 20); would throw an exception with what() message having both file, line and the actual values of a, and so on. Only a macro would have the power for this. The debugger may be configured to break at 'throw' of the specific exception type.

答案10

得分: 5

我承认在不考虑适当的错误报告时使用了断言。然而,这并不意味着当正确使用时它们就没有帮助。

如果你想遵循“尽早崩溃”原则,它们尤其有用。例如,假设你正在实现一个引用计数机制。在代码的某些位置,你知道引用计数应该是零或一。并且假设如果引用计数错误,程序不会立即崩溃,而是在下一个消息循环时才会崩溃,这时将很难找出问题出在哪里。在检测错误的起源附近使用断言会有帮助。

英文:

I admit having used asserts while not considering proper error reporting. However, that doesn't take away that they are very helpful when used correctly.

They are especially useful for if you want to follow the "Crash Early" principle. For example suppose you're implementing a reference counting mechanism. At certain locations in your code you know that the refcount should be zero or one. And also suppose that if the refcount is wrong the program won't crash immediately but during the next message loop at which point it will be difficult to find out why things went wrong. An assert would have been helpful in detecting the error closer to its origin.

答案11

得分: 5

我非常不喜欢断言。虽然我不会说它们是邪恶的。

基本上,断言会执行与未检查异常相同的操作,唯一的例外是断言(通常)不应保留在最终产品中。

如果在调试和构建系统时为自己建立了一个安全网,为什么要拒绝为客户、支持帮助台或任何将使用您当前构建的软件的人提供这个安全网呢?只使用异常来处理断言和异常情况。通过创建适当的异常层次结构,您将能够迅速区分二者。除此之外,断言仍然存在,并且在失败时可以提供有价值的信息,否则这些信息将会丢失。

因此,我完全理解Go的创建者完全删除了断言,并强制程序员使用异常来处理情况。这有一个简单的解释,异常只是更好的机制,为什么要坚持过时的断言呢?

英文:

I dislike asserts intensely. I would not go as far as saying they are evil though.

Basically an assert will do the same thing as an unchecked exception would, the only exception is that the assert (normally) should not be kept for the final product.

If you build a safety net for yourself while debugging and building the system why would you deny this safety net for your customer, or your support help desk, or anyone that will get to use the software that you are currently building. Use exceptions exclusively for both asserts and exceptional situations. By creating an appropriate exception hierarchy you will be able to discern very quickly one from the other. Except this time the assert remains in place and can provide valuable information in case of failure that would otherwise be lost.

So I fully understand the creators of Go by removing asserts altogether and forcing programmers to use exceptions to handle the situation. There is a simple explanation for this, exception are just a better mechanism for the job why stick with the archaic asserts?

答案12

得分: 3

如果你所说的断言意味着程序出错然后退出,那么断言可能是非常糟糕的。这并不是说它们总是错误的使用方式,它们是一种非常容易被滥用的结构。它们也有许多更好的替代方案。这些类似的东西很容易被称为邪恶。

例如,第三方模块(或任何模块)几乎永远不应该退出调用程序。这不会给调用程序员在那一刻控制程序应该承担的风险。在许多情况下,数据非常重要,即使保存了损坏的数据也比丢失数据要好。断言可能会导致数据丢失。

一些替代方案:

  • 使用调试器
  • 控制台/数据库/其他日志记录
  • 异常
  • 其他类型的错误处理

一些参考资料:

即使是支持断言的人也认为它们只应该在开发中使用,而不应该在生产中使用:

这个人说,当模块在抛出异常后可能存在持久损坏的数据时,应该使用断言:http://www.advogato.org/article/949.html。这当然是一个合理的观点,然而,外部模块绝对不应该规定损坏数据对调用程序的重要性(通过“为”它们退出)。正确的处理方式是抛出一个清楚地表明程序可能处于不一致状态的异常。由于良好的程序主要由模块组成(主执行文件中有一些粘合代码),断言几乎总是错误的做法。

英文:

If the asserts you're talking about mean that the program vomits and then exists, assertions can be very bad. This is not to say that they are always the wrong thing to use, they are a construct that is very easily misused. They also have many better alternatives. Things like that are good candidates for being called evil.

For example, a 3rd party module (or any module really) should almost never exit the calling program. This doesn't give the calling programmer any control over what risk the program should take at that moment. In many cases, data is so important that even saving corrupted data is better than losing that data. Asserts can force you to lose data.

Some alternatives to asserts:

  • Using a debugger,
  • Console/database/other logging
  • Exceptions
  • Other types of error handling

Some references:

Even people that advocate for assert think they should only be used in development and not in production:

This person says that asserts should be used when the module has potentially corrupted data that persists after an exception is thrown: http://www.advogato.org/article/949.html . This is certainly a reasonable point, however, an external module should never prescribe how important corrupted data is to the calling program (by exiting "for" them). The proper way to handle this is by throwing an exception that makes it clear that the program may now be in an inconsistent state. And since good programs mostly consist of modules (with a little glue code in the main executable), asserts are almost always the wrong thing to do.

答案13

得分: 3

我最近开始在我的代码中添加一些断言,这是我做的方式:

我在心里将我的代码分为边界代码和内部代码。边界代码是处理用户输入、读取文件和从网络获取数据的代码。在这段代码中,我会在一个循环中请求输入,只有在输入有效时才退出循环(对于交互式用户输入),或者在文件/网络数据损坏的情况下抛出异常。

内部代码是其他所有的代码。例如,一个设置类变量的函数可以定义为:

void Class::f (int value) {
    assert (value < end);
    member = value;
}

而从网络获取输入的函数可能会这样写:

void Class::g (InMessage & msg) {
    int const value = msg.read_int();
    if (value >= end)
        throw InvalidServerData();
    f (value);
}

这给了我两层检查。任何在运行时确定数据的地方都会得到一个异常或立即的错误处理。然而,在Class::f中使用assert语句的额外检查意味着,如果一些内部代码调用了Class::f,我仍然有一个健全性检查。我的内部代码可能不会传递一个有效的参数(因为我可能已经通过一系列复杂的函数计算出了value),所以我喜欢在设置函数中使用断言来记录,无论是谁调用该函数,value都不能大于或等于end

这似乎符合我在一些地方读到的内容,即断言在一个良好运行的程序中是不可能违反的,而异常应该用于异常和错误的情况,这些情况仍然是可能的。因为理论上我正在验证所有的输入,所以我的断言不应该被触发。如果触发了,那么我的程序是错误的。

英文:

I've recently started adding some asserts to my code, and this is how I've been doing it:

I mentally divide my code into boundary code and internal code. Boundary code is code that handles user input, reads files, and gets data from the network. In this code, I request input in a loop that only exits when input is valid (in the case of interactive user input), or throw exceptions in the case of unrecoverable file / network corrupt data.

Internal code is everything else. For instance, a function that sets a variable in my class might be defined as

void Class::f (int value) {
    assert (value &lt; end);
    member = value;
}

and a function that gets input from a network might read as such:

void Class::g (InMessage &amp; msg) {
    int const value = msg.read_int();
    if (value &gt;= end)
        throw InvalidServerData();
    f (value);
}

This gives me two layers of checks. Anything where the data is determined at run-time always gets an exception or immediate error handling. However, that extra check in Class::f with the assert statement means that if some internal code ever calls Class::f, I still have a sanity check. My internal code might not pass a valid argument (because I may have calculated value from some complex series of functions), so I like having the assertion in the setting function to document that regardless of who is calling the function, value must not be greater than or equal to end.

This seems to fit into what I'm reading in a few places, that assertions should be impossible to violate in a well-functioning program, while exceptions should be for exceptional and erroneous cases that are still possible. Because in theory I'm validating all input, it should not be possible for my assertion to be triggered. If it is, my program is wrong.

答案14

得分: 2

是的,断言是有害的。

通常它们被用在应该使用适当的错误处理的地方。要习惯从一开始就编写适合生产环境的错误处理!

通常它们会妨碍编写单元测试(除非你编写一个与测试工具交互的自定义断言)。这通常是因为它们被用在应该使用适当的错误处理的地方。

大多数情况下,它们会在发布版本中被编译掉,这意味着当你运行实际发布的代码时,它们的“测试”都不可用;考虑到在多线程情况下,最严重的问题通常只会在发布代码中出现,这可能是不好的。

有时它们是对本来有问题的设计的一种支撑;也就是说,代码的设计允许用户以不应该的方式调用它,而断言“阻止”了这种情况。修复设计!

我在2005年的博客中更详细地讨论了这个问题:http://www.lenholgate.com/blog/2005/09/assert-is-evil.html

英文:

Yes, asserts are evil.

Often they get used in places where proper error handling should be used. Get used to writing proper production quality error handling from the start!

Usually they get in the way of writing unit tests (unless you write a custom assert that interacts with your test harness). This is often because they are used where proper error handling should be used.

Mostly they get compiled out of release builds which means that none of their "testing" is available when you're running the code that you actually release; given that in multi-threaded situations the worst problems often only show up in release code this can be bad.

Sometimes they're a crutch for otherwise broken designs; i.e. the design of the code allows a user to call it in a way that it shouldn't be called and the assert "prevents" this. Fix the design!

I wrote about this more on my blog back in 2005 here: http://www.lenholgate.com/blog/2005/09/assert-is-evil.html

答案15

得分: 1

简短回答:不,我认为断言可以很有用。

英文:

Short answer: No, I believe assertions can be useful

答案16

得分: 1

assert非常有用,当出现意外错误时,它可以在出现问题的最初迹象处停止程序,从而节省了很多回溯的时间。

另一方面,滥用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;
}

所以...从长远来看...从大局来看...我必须承认assert可能会被滥用。我经常这样做。

英文:

assert is very useful and can save you a lot of backtracking when unexpected errors occur by halting the program at the very first signs of trouble.

On the other hand, it is very easy to abuse assert.

int quotient(int a, int b){
    assert(b != 0);
    return a / b;
}

The proper, correct version would be something like:

bool quotient(int a, int b, int &amp;result){
    if(b == 0)
        return false;

    result = a / b;
    return true;
}

So... in the long run... in the big picture... I must agree that assert can be abused. I do it all the time.

答案17

得分: 1

“assert”被滥用于错误处理,因为它输入更少。

因此,作为语言设计者,他们应该更加注重使用更少的输入来实现适当的错误处理。不要因为异常机制冗长而排除assert。哦,等等,Go也没有异常。太糟糕了 断言是邪恶的吗?

英文:

assert is being abused for error handling because it is less typing.

So as language designers, they should rather see that proper error handling can be done with even lesser typing. Excluding assert because your exception mechanism is verbose is not the solution. Oh wait, Go doesn't have exceptions either. Too bad 断言是邪恶的吗?

答案18

得分: 1

我对这些为assert辩护的回答的问题是,没有人清楚地说明它与普通的“致命错误”有何不同,以及为什么assert不能是异常的一种子集。现在,假设异常从未被捕获,那么它是否可以被称为断言?而且,为什么你会希望在语言中强加一个异常可以被引发而“无法处理”的限制呢?

英文:

My problem with these answers defending assert is no one clearly specifies what makes it different from a regular fatal error, and why an assert can't be a subset of an exception. Now, with this said, what if the exception is never caught? Does that make it an assertion by nomenclature? And, why would you ever want to impose a restriction in the language that an exception can be raised that /nothing/ can handle?

答案19

得分: 1

我从不使用assert(),示例通常会显示类似以下的内容:

int* ptr = new int[10];
assert(ptr);

这是不好的,我从不这样做,如果我的游戏正在分配一堆怪物,为什么要崩溃游戏呢?相反,你应该优雅地处理错误,所以可以这样做:

CMonster* ptrMonsters = new CMonster[10];
if(ptrMonsters == NULL) // 或者你可以直接写成 if(!ptrMonsters)
{
    // 我们无法分配怪物。记录错误,例如“无法生成10个怪物”。
}
else
{
    // 初始化怪物。
}
英文:

i never use assert(), examples usually show something like this:

int* ptr = new int[10];
assert(ptr);

This is bad, i never do this, what if my game is allocating a bunch of monsters? why should i crash the game, instead you should handle the errors gracefully, so do something like:

CMonster* ptrMonsters = new CMonster[10];
if(ptrMonsters == NULL) // or u could just write if(!ptrMonsters)
{
    // we failed allocating monsters. log the error e.g. &quot;Failed spawning 10 monsters&quot;.
}
else
{
    // initialize monsters.
}

答案20

得分: 0

不是那么邪恶,而是普遍具有反生产力。永久性错误检查和调试之间存在分离。断言使人们认为所有调试都应该是永久性的,并且在大量使用时会导致可读性问题。在需要时,永久性错误处理应该更好,而且由于断言会引发自己的错误,这是一个相当可疑的做法。

英文:

Not so much evil as generally counterproductive. There's a separation between permanent error checking and debugging. Assert makes people think all debugging should be permanent and causes massive readability problems when used much. Permanent error handling ought to be better than that where needed, and since assert causes its own errors it's a pretty questionable practice.

答案21

得分: 0

当我看到那个时,我感觉想踢作者的头。

我在代码中经常使用断言,并在编写更多代码时逐渐替换它们。当我还没有编写所需的逻辑并且希望在运行代码时得到警告时,我会使用它们,而不是编写一个在项目接近完成时将被删除的异常。

异常也更容易与生产代码融合,这一点我不喜欢。断言比throw new Exception("一些通用消息或'假装我是一个断言'");更容易被注意到。

英文:

I felt like kicking the author in the head when I saw that.

I use asserts all the time in code and eventually replace them all when I write more code. I use them when I haven't written the logic required and want to be alerted when I run into the code instead of writing an exception which will be deleted as the project gets closer to completion.

Exceptions also blend in with production code more easily which I dislike. An assert is easier to notice than throw new Exception(&quot;Some generic msg or &#39;pretend i am an assert&#39;&quot;);

huangapple
  • 本文由 发表于 2009年12月6日 12:04:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/1854302.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定