I’ve been thinking a lot lately about software – how to design and write software that works. One thing that has struck me is that the use of assertions is actually bad in the long run for any code base.
Assertions are like training wheels. Immature code uses them because immature code falls down, a lot at first. Presumably, your code will grow up some day, but if you keep using assertions, that will be harder to achieve. In fact, assertions subtly guide your code away from taking full responsibility and your code will always stay in a non-robust state.
Assertions are not error handling, but despite saying that, the mere fact of having assertions in the code means that there are either logic bugs waiting, or you have error handling masquerading as assertions. And since most people turn assertions off in release code, this means that you also turn off your error handling.
It’s worse than that, because assertions don’t make for good error handling. While aborting is certainly one way to handle an error, and while exception handling reporting (often employed for assertions) makes it a little easier to do a postmortem diagnosis, it’s always better to actually handle errors at run-time, so that things can still work for the user.
The first step is to stop thinking as if you need assertions. You don’t. If you look at most of the places in the code where you use assertions, you’ll see that they fall into two groups – hedges against logic bugs like
assert(ptr != NULL), and then bad error handling like
assert(*url != 0). In the former, you suspect there might be a bug now, or introduced in the future. In the latter, you know that an empty URL is illegal to pass in.
There are an infinite number of bugs that might exist, and scattering the code with assertions to find them is a hopeless task. You’re trying to prove the negative, which is impossible. Instead, you want to change your development methods so you can prove the positive, i.e. prove that your code is bug-free. This is very hard, but not impossible. Since using assertions to find all bugs is impossible, and since your goal should be to have no bugs, assertions are a dead-end in that regard.
Assertions in fact aren’t error handling, they are error detection. But since our brains aren’t perfect, when we see an assertion on some error state, we think “ok, that’s covered”, and move on, leaving a time-bomb behind that will go off at some point in the future.
Assertions are a viable tool when you are exploring some new space. It’s like using debugger breakpoints, or print statements; you use them to double-check your assumptions, to try things as you are putting them together. But they should quickly come out of the code once you know what you are doing.
Should library code have assertions to help catch errors in new code using solid library code? That’s dubious. Perhaps the only good case I’ve seen of this is the idea that Microsoft had of the “checked build” of Windows, which had the equivalent of assertions, runtime checks that would go off if you passed bad parameters etc. But end-users didn’t use the checked build, it was strictly a testing tool. However, that doesn’t map well to having assertions in library code, because of the negative effect that assertions have on the code around them.