5

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.

Community
  • 1
  • 1
a06e
  • 18,594
  • 33
  • 93
  • 169
  • Are you never go share any of this code/make it a library or will anyone else have to take over this code at some point? You should try to make code as maintainable a portable as possible – NathanOliver Aug 18 '15 at 14:33
  • @NathanOliver Not in the near future. I share it with students, or close collaborators. But as you say, I do care about maintainability. However, to apply Herb Sutter's rule, I have to draw arbitrary lines in my code, to define what's "internal" to a given module, and what's external. I haven't really found any use for that. – a06e Aug 18 '15 at 14:42
  • Historically using asserts is a 'C' technique. Using exceptions is a 'C++" technique. This may make a difference in your thinking about their purpose. Asserts should be used to detect conditions that cannot possibly ever happen. but if it does I want to know so I can debug the code. – Dale Wilson Aug 18 '15 at 15:53
  • Usualy assert is for developper only in debug mode only for checking crazy errors.All your assert are removed in the release build for speed. – ColdCat Aug 18 '15 at 16:01
  • 1
    btw https://giantfublog.wordpress.com/2015/03/11/errors-and-exceptions/ is one take on the subject – stijn Aug 21 '15 at 10:43
  • @stijn Thanks for the reference. Good read. – a06e Aug 21 '15 at 14:26
  • 1
    Forgot to mention, there's also the corresponding [HN thread](https://news.ycombinator.com/item?id=10095678) where there's quite some disagreement with the article, of course. – stijn Aug 21 '15 at 15:01

3 Answers3

5

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.

SirDarius
  • 41,440
  • 8
  • 86
  • 100
1

I'm in a similar situation; engineering software, sole developer, very few users of my programs. My rule of thumb is to use exceptions when the program could feasibly recover from an error, or when a user should be expected to react to the error in some way. An example is checking for negative numbers where only positive numbers are allowed: the program doesn't need to terminate because the user typed in a negative value for mass, they just need to recheck their inputs and try again.

On the other hand, I use asserts to catch major bugs in the software. In the event that some error occurs from which the program has no hope of recovering (or that the user has no hope of fixing themselves), I just let the assert print out the file name and line number so that the user can report it to me, and I can fix it. An example of where I would use an assert is checking that the number of rows and columns of a matrix are equal when I'm expecting a square matrix. If num_rows != num_cols, then something is seriously broken with the code and some debugging is required. In my opinion, this is easier than trying to imagine all the possible ways that a matrix could become invalid, and test for them all.

As far as performance, I only disable or remove asserts and other error checks in critical sections of code, and then only when that section has been thoroughly tested and debugged.

My approach is probably not good for production software though. I can't imagine some program like Microsoft Excel bombing out with an "assertion failed" message. Ha ha. It's one thing if the three coworkers who use your software complain about your error-handling strategy, but quite another if you have thousands of unhappy customers who paid cash for it.

Carlton
  • 4,217
  • 2
  • 24
  • 40
  • 2
    I like the distinction that an assert should indicate "coding (and possibly debugging) is required" whereas an exception means the code is fine, the data is broke. – Dale Wilson Aug 18 '15 at 15:48
0

I'd use assertions where I expect the check to have a performance impact. I.e. when writing a vector or matrix class of simple types (e.g. double, complex<double>), and I wanted to make a bounds check I'd use assert(), because the check there has a potentially large performance impact, since it happens with every element access. I can then turn off this check in production builds with -DNDEBUG.

If the cost of the check does not matter (e.g. a check that an initial solution does not contain NaN values before you pass it to an iterative scheme), I would use an exception or another mechanism that is also active in production builds. If your job aborts after waiting in the queue of a cluster for three days and running for 10 hours, you at least want to have a diagnostic better than "killed (SIGSEGV)", so you can avoid rebuilding in debug-mode, waiting another 3 days and spending another 10 hours of expensive computation time.

The are situations where neither exceptions nor asserts are appropriate. An example would be an error where the cost of checking does not matter, but that is nevertheless fatal enough that the program cannot continue under any circumstances. An assertion is not appropriate, because it only triggers in debug mode, an exception is not appropriate, because it can (accidentally) be caught, obscuring the problem. In such a case I'd use a custom assert macro, that does not depend on NDEBUG, e.g.:

// This assert macro does not depend on the value of NDEBUG
#define assert_always(expr)                                          \
  do                                                                 \
  {                                                                  \
    if(!(expr))                                                      \
    {                                                                \
      std::cerr << __FILE__ << ":" << __LINE__ << ": assert_always(" \
                << #expr << ") failed" << std::endl;                 \
      std::abort();                                                  \
    }                                                                \
  } while(false)

(This example was taken from here with a modified name to indicate the slightly broader purpose).

ex-bart
  • 1,352
  • 8
  • 9
  • Asserts are handy to document programmer assumptions etc, where they really shine is uncovering bugs which "should never happen" but in real life do happen because e.g. forgetting to initialize a variable. Such bugs might not propagate in debug builds but only in release builds. In which case a dialog saying "assert bla bla file x line y" is also way better than "killed (SIGSEGV)". tldr; it might be a good idea to have assert macros which are enabled in release builds as well and then the performance argument becomes moot – stijn Aug 18 '15 at 15:03
  • 1
    @stijn I updated the answer to include a macro like you said as a third way. – ex-bart Aug 18 '15 at 15:42
  • Just for the record, it is extremely rare (not impossible, though) for the checking needed to decide whether to throw an exception to have a measurable impact on the performance. Beware micro optimizations. Use asserts to detect bugs, and exceptions for everything else. – Dale Wilson Aug 18 '15 at 15:50