Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Asserts are essentially checked comments and an automated test is not a replacement for comments.

I've done things like the following many times:

    if (x != 5) {
        ... 40 lines here ...
    } else {
        assert(x == 5);
        ... more lines ...
    }
The main purpose of the assert is to remind the reader that x == 5. There was a time where I would use a regular comment for a reminder like this, but since it's something the computer can check, I mine as well turn my regular comment into a checked comment.


The danger is change. What if someone comes and then due to a new business requirement changes the code to

    if (x != 5 && x != 6) {
        ... 40 lines here ...
    } else {
        assert(x == 5);
        ... more lines ...
    }
Because of the 40 intervening lines the person might never see your assertion!

If you work in a project with automated tests that do not have sufficient coverage, this is just a spectacular way of blowing up production.

Assertions are bad because they are checked at runtime which is too late. The ideal checked comment should have the checks happen at compile time. Something like Liquid Haskell.


> this is just a spectacular way of blowing up production.

If your devs and QA team (you do have QA and not just rely on automated tests I hope) don't trigger this assert *before* deployed to production then there's something seriously wrong with your development workflow.

After all, the code after that assert also assumes that the (now invalid) assumption that was checked by the assert is true. Without the assert telling you exactly what is wrong and where, you'd have a much bigger problem: some piece of code that now runs under wrong assumptions, and which may or may not cause much harder to diagnose problems further down the road.

Think of asserts as trip-wires, they are most useful at the entry of a function and prevent an error from propagating (because the earlier an error condition is caught, the easier it is to diagnose).


Do you have QA running debug builds? In "release" builds, the asserts are compiled out. (Not sure what language and platform you're using)

Or does QA switch to release builds at some point?

When asserts fail for QA, what do they see/collect?


> In "release" builds, the asserts are compiled out.

That's just a convention, and IMHO not a good one. All the C++ games I helped shipping had asserts enabled in the release exe. We had our own custom assert macros, and different levels of assert checks (e.g. special "hot path asserts" were not included in release builds, but that was an exception and should be avoided - specifically, range-check asserts for array accesses were never removed since this was the most common source of problems). In general, the performance difference between keeping asserts in or removing them was mostly negligible (IIRC less than 2% in a typical frame), the only downside is that the executable becomes about 25% larger (and easier to reverse engineer) because of the embedded assert condition strings. This would have been solvable by replacing the assert strings with comptime hashes, but we didn't deem this important enough to implement).

In addition, the custom asserts also generated a stack trace and a mini dump for easier post mortem debugging.

> When asserts fail for QA, what do they see/collect?

A MessageBox with:

- the assert condition string

- filename and line number of the assert

- an optional printf-style programmer message (depending on the assert macro type)

- the pretty-printed function/method name which contains the assert (very useful for generic code, e.g. you don't just see `Array<T>::push()` but also the resolved template parameters)

- a stack trace (we generated a PDB file also for release builds to make the 'inhouse' stack traces human readable, but didn't ship the PDB to users - so stack traces for the shipped game would just display raw addresses, but those could be resolved on our side with the PDB we were storing for each release version - we wrote an extra inhouse tool for that)

- plus a mini dump is written for post-mortem debugging, but with all the information above that was hardly ever needed for figuring out what the problem was

The content of Windows MessageBoxes can be Copy-Pasted, and that's what QA was supposed to do when writing a ticket.

PS: also important to note that we didn't use the C++ stdlib (with very few exceptions, like std::sort), e.g. most importantly we wrote our own container and string types).


Thanks for the details, sounds like a great setup. Any GitHub repos you can recommend?

Except for writing your own stdlib. How long did that take? Was that to enhance bounds checking?


It's all inhouse code unfortunately, and from a previous life :)

What I still do in my open source libraries is to allow overriding the assert macro so that people can integrate their own assert implementation (along with allowing to override memory allocation).

> Except for writing your own stdlib. How long did that take? Was that to enhance bounds checking?

TBH the main reason was that I think the C++ stdlib APIs have terrible ergonomics. We modelled our container classes after C# containers and some common sense. Full control over memory allocation and asserts was just a side effect.


> If you work in a project with automated tests that do not have sufficient coverage, this is just a spectacular way of blowing up production.

Great, I'd rather have this error cause a loud and obvious failure that can be easily hotfixed, than have it lead to subtly incorrect behavior that goes unnoticed for days and takes another day to debug.


Then the assert() triggers and you fix the assert() (or the bug). What's the issue here?


In this example the assert is less explanatory than a comment. Why do

    if (condition):
        ...
    else:
        assert(!condition)
        ...
When you could instead if (condition): ... else: #if (condition) ...

Or otherwise explicitly comment the condition that was checked earlier. At least then if the comment is unchanged, whoever is touching your code will assume the comment is outdated, rather than assuming that you're asserting that x should be some undocumented magic number.


What if `... more lines ...` depends on `x == 5`? Then you'd want the compiler to catch the assertion error, rather than whatever obscure error is thrown later on. And if it breaks production, you are breaking production without the assertion; runtime assertions aren't ideal but they're better than nothing.

If you don't rely on `x == 5` in the else branch it's a bad idea, and too many of these spurious assertions will create a habit of just removing assertions whenever they fail.


Depends on your goals for production. The code above indicates a bug. In some domains it really is preferable to crash with righteous fury rather than to continue executing with broken invariants and doing who knows what.

The assertion didn't cause you to have limited test coverage that let you edit the branch condition without detecting that it introduced a bug in the false branch.

Yes, if you've got a language that supports compile time checking of complex invariants then that's awesome! But few languages support this today.


The problem with crashing is that it is almost always a bad idea. You almost always want a limited scope crash that affects whatever currently is being processed. Imagine that in some languages an assertion raises an AssertionError exception which is caught by some faraway handler that causes the server the respond with a 500 error. That's strictly better than crashing.

I also used to think it might be preferable to crash in some domains. With stories like https://news.ycombinator.com/item?id=37461695 it makes me think perhaps there are none.

In any case my main point is that assertions would be perfect if only they are checked at compile time.


> blowing up production.

I prefer what Java does: Assertions are disabled at runtime by default, however you can turn them on without requiring a different code-release and you can even do it for specific packages/classes.

So you might even use them in production: If feature X is already failing in some bizarrely uninformative way, turning on assertions in relevant code may provide you a better hint to what's going awry.


Well use static_assert then. Not all checks can be done at runtime.

Also isn't failing the assertion the whole point here? Without the assertion you would have a possibly hard-to-find bug rather than an assertion failure.


The entire point of failing fast is to fail fast.


Assertions do not fail fast enough. They need to fail at compile time as much as possible not at runtime.


> They need to fail at compile time as much as possible not at runtime.

Wouldn't that be nice! Seems like you're conflating two different classes of failures, though. There's no way to bypass runtime constraints entirely, though—all you can do is move it out of the production code and into tests, which takes a great deal of effort compared to an assert (which work just as well under tests, btw).


I'm not talking about tests. I'm talking about the compiler.


That's not possible. If I'm about to write some code that assumes the gyroscope is spinning, I might assert that the gyroscope is spinning. That cannot be checked at compile time.

Also, until almost all the world's software has been rewritten in Idris, compile time checks just cannot do very much. This is a reality of our century.

Keep in mind that my original example isn't really about checking that x == 5. The focus is not on checking integer values, it's just an example. Although, again, even something as simple as checking integer values is beyond the compile time abilities of our compilers. Halting problem sends its regards!


But how do you know that the gyroscope is or isn't spinning when that code is written? You don't. You might as well do a regular check for if the gyroscope is spinning, and if not return an error.

You do not need to have the entire world's software rewritten in Idris (which I don't even like that much). This is purely local reasoning that can be accomplished by minor changes to existing languages if we want to. The compiler doesn't need access to the world. It is you who are unnecessarily raising the bar for what you expect compilers to do and then throwing in the towel just because the compiler can't actually do it.

Any argument that invokes the halting problem has a standard response: you don't need to handle every case; perfect is the enemy of good. Furthermore it is often better to have the programmer rewrite the code to help the compiler deduce these facts, because helping the compiler also helps future readers of the code.


> You might as well do a regular check for if the gyroscope is spinning, and if not return an error.

I mean yea, it'd be great if programs propagated every possible error state or violated invariant possible up the stack. I don't see that happening any time soon, nor that being worth the effort. When was the last time you checked and propagated allocation failure? I'm guessing not in a long time, unless you're working with a runtime where allocation failure is explicitly handled as part of normal program progression (like the `alloc` libc family) and where you expect to run into memory limits.

Granted, exceptions are a good deal easier to handle "invisibly", but those have notably been absent from this discussion except in the case where they are unhandled as an assert might trigger.


This [1] might be a better example. C isn't Pascal, you can't just define a type of integer with a fixed range, so here, I check to ensure that the code above has done its job. I don't want an if statement here for this, for if the code above works, the if will never be triggered, and thus, as far as I'm concerned, is dead code.

[1] https://github.com/spc476/mod_blog/blob/master/src/backend.c...




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: