Exceptions vs assert for a computer science man (I'm the only user of my code)?

advertisements

Exceptions vs assert has been asked here before: Design by contract using assertions or exceptions?, Assertion VS Runtime exception, C++ error-codes vs ASSERTS vs Exceptions choices choices :(, Design by contract using assertions or exceptions?, etc. (*) There are also books, like Herb Sutter's Coding Standards that talk about this. The general consensus seems to be this:

Use assertions for internal errors, in the sense that the user of the module and the developer are one and the same person/team. Use exceptions for everything else. (**)

This rule makes a lot of sense to me, except for one thing. I am a scientist, using C++ for scientific simulations. In my particular context, this means that I am the sole user of most of my code. If I apply this rule, it means I never have to use exceptions? I guess not, for example, there are still I/O errors, or memory allocation issues, where exceptions are still necessary. But apart from those interactions of my program with the "outside world", are there other scenarios where I should be using exceptions?

In my experience, many good programming practices have been very useful to me, in spite of those practices being designed mostly for large complex systems or for large teams, while my programs are mostly small scientific simulations which are written mostly by me alone. Hence this question. What good practices of exception use apply in my context? Or should I use only asserts (and exceptions for I/O, memory allocation, and other interactions with the "outside world")?

(*) I hope that after reading the complete question, you agree that this is not a duplicate. The topic of exceptions vs assert has been dealt with before in general, but, as I try to explain here, I don't feel that any of those questions addresses my particular situation.

(**) I wrote this with my own words, trying to resume what I've read. Feel free to criticize this statement if you feel it does not reflect the majority's consensus.


assert() is a safeguard against programmer mistakes, while exceptions are safeguards against the rest of existence.

Let's explain this with an example:

double divide(double a, double b) {
    return a / b;
}

The obvious problem of this function is that if b == 0, you'll get an error.

Now, let's assume this function is called with arguments which values are decided by you and only you. You can detect the problem by changing the function into this:

double divide(double a, double b) {
    ASSERT(b != 0);
    return a / b;
}

If you have accidentally made a mistake in your code so that b can take a 0 value, you're covered, and can fix the calling code, either by testing explicitely for 0, or to make sure such a condition never occurs in the first place.

As long as this assertion is in place, you will get some level of protection as the developer of the code. It is a contract that makes it easy to see what kind of problem can occur in the function, especially while you are testing your code.

Now, what happens if you have no control over the values that are passed to the function ? The assertion will just disrupt the flow of the program without any protection whatsoever.

The sensible thing to do is this:

double divide(double a, double b) {
    ASSERT(b != 0);
    if (b == 0)
        throw DivideByZeroException();
    return a / b;
}

try {
    result = divide(num, user_val);
} catch (DivideByZeroException & e) {
    display_informative_message_to_user(e);
}

Note that the assertion is still in place because it is the most readable indication of what can go wrong. The addition of the exception, however, allows you to recover more easily from the problem.

It can be argued that such an approach is redundant, but in a release build, the assertions will usually be NOOPs without generated code, so the exception remains the sole protection.
Also, this function is very simple, so the assertion and the exception throw are immediately visible, but with a few dozens of lines of code added, that would not be the case anymore.

Now, when you are developing and likely to do mistakes, the assertion failure will be visible at exactly the line where it occured, while the exception might bubble up into an unrelated try/catch block that would make it harder to pintpoint the problem exactly, especially if the catch block does not log stack traces.

So, if you want to be safe and mitigate the risks of mistakes during development and during normal execution, you can never be too careful, and might want to provide both mechanisms in a complementary way.