This post is a response to my recent encounters with fellow programmers who appear to me to be missing the point of assertions and fail to appreciate their usefulness. The first post I have ever written here was on assertions, I still find it good, so there is no need to repeat it; here I will only describe how I observe people treat assertions and why I believe it is wrong.

I am reviewing the following code.

rational<int> process(rational<int> x, rational<int> dx) { if (!x) return 0; rational<int> ans = x / dx; // ... return ans; }

Template rational is Boost.Rational: it implements a mathematical concept of a rational number by storing a pair of integers. I say to my colleague:

There is a chance that you will be dividing by 0 (causing UB, a crash, etc.).

He says,

No, whenever dx is 0, x is also 0 (and vice versa). This is how the program works. So the if-statement will handle it, so I am confident it never happens.

I say,

Why don’t you put an assertion then, so that everyone knows what you just explained to me, like this:

rational<int> process(rational<int> x, rational<int> dx) { assert ((x != 0) == (dx != 0)); if (x == 0) return 0; assert (dx != 0); rational<int> ans = x / dx; // ... return ans; }

And he replies,

I don’t want to do this, because it might cause a crash.

— short pause —

At this point I realized something. Not sure what exactly, but I would describe this as follows. There are different kinds or strengths of perception that some event would never occur. Apparently, when you just type a division the perception that the divisor would never be zero is strong enough, to let it go unchecked. And apparently, just planting an assert, or even the possibility of planting the assert, changes this perception. It looks that some risks of undefined behaviors appear at sub-conscious level, and an assertion helps move the risk into the conscious level.

One more reason to use assertions. They make things more conscious and objective.

In the end my colleague changed the code to this:

rational<int> process(rational<int> x, rational<int> dx) { if (x == 0 || dx == 0) return 0; rational<int> ans = x / dx; // ... return ans; }

Which was not necessarily the best move. If there is a precondition to your function, you can try to make it explicit rather than turning it into a defensive if.

For another interesting story, I am reviewing the following code:

void populate(vector<Record>& vec) { for (/*...*/) vec.push_back(/*...*/); }

I say to my colleague,

You probably do not expect this vector to be non-empty upon entry, why don’t you put an assertion to indicate that?

He replies,

This function is only called in one place and upon a default-constructed vector, so there is no single chance this will be non-empty, so there is no point in asserting that.

— short pause —

To my knowledge, the only place where you put assertions is where you are sure that the situation never ever occurs (unless we have a bug). This is what they are for!