Reputation: 20774
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.
Upvotes: 5
Views: 934
Reputation: 1372
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).
Upvotes: 0
Reputation: 42889
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.
Upvotes: 5
Reputation: 4297
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.
Upvotes: 1