Assertion vs Exception Handling

J

James Kanze


[...]
They have the concept of checked iterators,

So does g++. And Sun CC if you use the STLPort.
which is very similar to asserts, but not implemented via
assert(). And this feature is on also in Release builds by
default.

Sort of. Like most modern compilers, Microsoft has a fairly
wide variety of debugging options: NDEBUG is just the tip of the
iceberg. By default (but who's stupid enough to use the
defaults), you get all of them in "debug" mode; some (but not
all) are turned off in release mode, and you can turn on or off
any in either mode (or create a third mode, if that's
appropriate for some reason).
One has to take extra steps to turn it off. Ok, I understand
that as this feature affects binary compatibility, they would
like to to have the same setting in Debug and Release builds
so that the libraries from different builds can be mixed.

Except that they can't, really. (At least not pre-VC10.0.)
I've encountered a number of problems because of incompatible
debugging options when using std::string. (The other classes
don't seem to be affected.)
Though, I would consider a debugging feature affecting the
binary compatibility of libraries as a design failure.

Me too, but realistically... Microsoft does far better than
most in this regard, even if they aren't perfect.
 
I

Ian Collins

Apart from that, asserts do not always make sense in non-debug
code anyway. For example, I currently also develop a graphics
transformation library. If I build it with fully optimisation,
it becomes faster by a factor of 10-20 (among other things due
to massive inlining). However, it is more or less _impossible_
to debug when built fully optimised, so active asserts would not
do much good anyway.

Don't you have unit tests? Part of my definition of "release" is to
have the code as shipped pass all its unit tests. If your tests are
concise, debugging is straightforward regardless of optimisation.
By the way, the standard 'assert' macro defined in the standard
IIRC is explicitely disabled if 'NDEBUG' is defined, and setting
this preprocessor symbol also disables all sorts of range checks
etc. in STL containers at least in VC++, if I am not mistaken.
So in my opinion it could in fact be quite common that asserts
are inactive in production code, at least if it is
performance-critical code built with full optimisation.

Those other actions are specific to your compiler and library
combination. But I agree that performance critical code is a good
candidate for disabling asserts.
 
I

Ian Collins

And I'd rather release the executables that I've tested, rather
than something else.

Did I say otherwise?

I always run unit tests as part of a build. If the tests fail, the
build fail. The build and test process and the compiler options used
are completely independent.
 
R

Robert Fendt

Don't you have unit tests? Part of my definition of "release" is to
have the code as shipped pass all its unit tests. If your tests are
concise, debugging is straightforward regardless of optimisation.

Other than 'check if it crashes', unit tests are not
straight-forward in image morphing code, since you would have to
compare the result against reference images, which differ subtly
if you change e.g. interpolation and filtering settings. This
makes unit testing quite problematic, though I will not say it
cannot be done.

However, I was not talking about testing, but about the lack of
usefulness of running fully optimised code with lots of inlining
in a debugger and hoping to achieve something meaningful. At
least on GCC (which is one of my two development platforms) it's
absolutely useless since the function calls and corresponding
symbols don't even exist in the binary. The situation is
actually a bit less bad on VC++ since it leaves more information
intact even when inlining code. However, what I meant is: you
*test* the optimised build, and if there's a problem, you more
often than not have to go look in the debug build and try to
figure out why.

Regards,
Robert
 
C

Chris Gordon-Smith

James Kanze said:
Whether exception handling or assertions are more appropriate
depends on the type of error and the application domain. And
possibly other considerations as well.

I would be interested to know whether there are any guidelines on this. It seems to me
that exceptions are often useful in cases where the program itself has done nothing
wrong, but something unusual has happened in its environment.

For example, 'out of memory', or a network error. If the program's users value
availability, it is probably worth writing exception code to allow the program to
continue, possibly with some degradation of service.

On the other hand, if the error is in the program, there is a lot to be said for
terminating immediately before any more damage is done. This is the approach I am taking
for an Artificial Chemistry simulator I am developing. As it is a research tool there is
no requirement for high availability, and so the simplest thing to do is to use
assertions and terminate at the first sign of trouble.

Another case arises with large systems that do have high availability requirements but
are known to contain occasional logic errors. I recall a case a few years ago in the
'work' part of my life where a global shipment management system had a bug that
occasionally caused it to set shipment charges to zero! No-one could find the bug at
first, but terminating the program was not an option. We decided to adopt a policy of
containment, putting suspect transactions 'in quarantine'. The program was not written in
C++, but perhaps if it had been exception handling could have been used to detect the
condition and deal with it.

Chris Gordon-Smith
www.simsoup.info
 
A

Alf P. Steinbach

* Chris Gordon-Smith:
I would be interested to know whether there are any guidelines on this.

Sure. That's almost a given. But we're not a point yet where there's even any
consensus about the simplest things.

Consider the C++ standard library's exception hierarchy. I haven't heard about
anyone who use those exception types as their names indicate. Yet very good
brains were involved in forming the standard library.

Consider a language like Python where not only assertions are exceptions, but
some exceptions are used to signal success, like, hey, we exhausted an iterator
as expected, and there are even exceptions called ...Warning. :)

Personally I think exceptions, as they're commonly expressed in most of today's
languages, are at a too low level of abstraction, sort of like goto.

I like the Eiffel approach to exception handling where exceptions always
indicate failure and the syntax makes it clear what you can do when an exception
occurs: achieve the goal in some other way (e.g. by simple retrying), or fail in
turn. I think this could be particularly clean in C++ where most cleanup can be
achieved via RAII, thus removing that aspect from exception handling. And I once
started a few threads over in [comp.std.c++] suggesting such higher level
syntax, including a few formal definitions, but all that came out of that was
that Dave Harris (I think it was) showed me -- and other readers -- how the
template pattern can express the basic Eiffel exception handling notion.


Cheers,

- Alf
 
I

Ian Collins

Exceptions are for errors, that's what they were designed for. If your
program can handle some particular condition and continue normal
execution, how is that condition an error? The answer is, it isn't.

Nonsense. Just because an error condition can't be handled locally,
doesn't mean it can't be correctly handled elsewhere. For example if an
object fails to construct because a user specifies an absent file,
should the error be handled in the constructor, or passed to a higher layer?
Invalid user input is a good example of something that should *not*
cause an error in your program, your program should be able to deal with
such input gracefully.

True.
 
I

Ian Collins

It is true that such an error should be dealt with gracefully and
exceptions can be used to achieve this. Throwing an exception does not
and should not equal program termination in all cases.

Did I say otherwise?
 
C

Chris Gordon-Smith

Daniel T. said:
My basic guideline is that a catch should always re-throw, because if
your program can recover from the situation, then it isn't an error.

Noted, but that's a guideline about how to use exceptions, not about
when to use exceptions and when to use assertions.

Chris Gordon-Smith
www.simsoup.info
 
K

Kai-Uwe Bux

Daniel said:
"Something that can't be handled locally" seems to be a rather broad
definition of "error." Much too broad for me?

Maybe so, but the question is not whether something that cannot be handled
locally is an error. The question at hand is whether such a condition is a
good candidate for using the try-throw-catch facility of the language. Now
the "cannot be handled locally" seems to indicate that at the point in code
where the condition is first recognized, no reasonable prediction can be
made about the amount of backtracking / stack unwinding needed to deal with
it (that could happen, e.g., inside some library code that is written
without knowledge about client code). In that case, try-throw-catch seems to
be appropriate regardless of whether the detected condition qualifies as an
"error".

Of course, this decision involves a trade-off: try-throw-catch is a wicked
jump across an unknown amount of code, a jump that could take program flow
anywhere. It requires a good deal of care to program in the presence of such
jumps. Nonetheless, sometimes the alternatives are more clumsy, more
convoluted, and less understandable. In that case, I would opt for throwing.

[...]


Best

Kai-Uwe Bux
 
I

Ian Collins

In my experience, having a slew of catch blocks each dealing with a
different type of user input is even less graceful.

That's a pretty rare circumstance, I've certainly never seen it. A more
common scenario would be:

try {
doSomething();
doAnotherSomething();
doSomethingElse();
}
catch( const std::runtime_error& e ) {
dieOrRecover();
}

As an alternative to checking the return of all calls. This approach
tends to be both clearer (less clutter, al clean up in one place) and
more efficient (no tests after ever call that can fail).
The fundamental difference between us, I think, is how we define
"error." I define it as something the program cannot recover from. When
the design says, "If the user does X, then the program must to Y," I
don't call it an error when the user does X. If the design requires that
condition A must pertain at point B, and condition A does not pertain at
point B; I call *that* an error.

Exceptions are for exceptional circumstances. If something that
shouldn't fail fails, that's and exceptional circumstance. That's why I
mentioned file opening failing, if the file should be there, throw if it
isn't. If it's expected to sometimes not be there, deal with it on the
spot.

If something is guaranteed not to happen, assert if it does!
 
M

Michael Doubez

Actually I think embedded software is one of the exceptions where a higher
degree of defensiveness is required (especially where safety is a concern)
and a reboot is usually the best outcome for unexpected state.  On the other
hand I have worked in the mobile phone industry and from what I can recall
we didn't use release mode asserts much.

I also have an embedded software background but now I work in finance
and the rules are more or less the same: they prefer an error than a
program in an inconsistent state that will report erroneous value or
state to customer (I expect customers get upset when their algorithm
buy/sell at the wrong price).

The only difference is that they prefer to work in degraded mode (mark
a state inconsistent) rather than reboot the program when possible but
only because other mechanisms are used. Continuation of service is
important but correctness is generally preferred.

Well, it is an old argument.

Renaming NDEBUG into NASSERT would have help ... perhaps.
 
M

Michael Doubez

Nonsense, either be extremely defensive or not defensive at all, somewhere
inbetween is pointless and an assert before/after very state changing
function is overkill for lots of application domains.

When you are carrying human lives (and I include in that the lives of
people, their money and the product of their work), could you give me
and adequate level for not-overkill ?

When the state don't change so often, an assert is a little price,
helps to understand the code and provides a level of defense.

assert is a debug feature, the NDEBUG means "not debugging" and when we are
"not debugging" assert does nothing.

From the standard's point of view, NDEBUG means "assert have been
removed".

If you program by contract, it means: I run wihtout checking the terms
of the contract; which, as in real life, is fine until there i6s a
problem and you end up with only your pant (and a spare shirt).
 
N

Nick Keighley

I still disagree with assert being primarily a debugging aid.  Unit
tests should be the debugging tool, asserts are the safety net for those
unexpected events that can't be handled by the application's logic.

I may be biased coming from an embedded background, but one of the first
lessons an embedded developer learns is to expect the unexpected and
defend against it.

I was taught in my OS course "that anything that can happen, will
happen". This transferred nicely to RT programming. I too leave the
assert()s on. I may disable more expensive one (assert
(tree_is_vallid(tree)). but the cheap ones stay. How can you allow a
program to run on in an undefined state?
 
N

Nick Keighley

My basic guideline is that a catch should always re-throw, because if
your program can recover from the situation, then it isn't an error.

this seems like a matter of how you define things. It might be an
error in some subprogram that doesn't affect the system as a whole. In
high availability transaction oriented things it might be sensible to
abort the current "transaction" and continue. Telecomms stuff seems
well suited to this.

I've written (noddy) parsers where exceptions seem a fine way of
bailing out of a deeply nested RDP.

I've written C and Python versions of simple socket drivers. The
python looks much nicer because the error handling is separate.

When writing a function, if a precondition is violated, then an error
has been detected in the calling code, an error that this function
cannot fix (by definition.) Unless you know for a fact that every
program that could possibly use this function should abort whenever a
precondition is violated, without any chance to save data or otherwise
clean up, you should not use 'assert', throw an exception instead.

yes. I've wrappered Win-32 calls so they threw if an error occurred.
And in some cases I handle the error rather than terminate.
If a postcondition is violated, then the function was written
incorrectly. Chances are, you didn't write either an assert or exception
to cover such an issue, because if you knew the function wouldn't work,
you would have fixed the code rather than adding code to notify the
caller that your code is broken. Whenever a postcondition violation
takes place, you should fix the code so it can't happen anymore. It
would not be appropriate to leave the bug in and add an assertion or
exception to notify the caller that your code doesn't work.

this is why linux never panics
 
M

Michael Doubez

This sounds like a nightmare and completely wrong: if you have code that can
determine that something is seriously wrong then you should be using release
mode asserts and terminate the program rather than trying to continue in
some "degraded mode".  The alternative is to have no release mode asserts
whatsoever and perhaps rely on a select few exceptions in core software
components (e.g. a container checking for bounds overrun) and rely on
program terminating (or crashing) in a predictable way... :)

You can also isolate a functionality or some handling of the data.

In this specific case, some precondition of the program (such as
entries from a data flow that *should* be unique) are not respected.
This would normally lead to a logic error for the associated parts
which rely on this precondition. In this case, the program raises an
alert (a big one), offending data is discarded and further use of the
state associated to it is marked as inconsistent.

Here, it is better than trying to explain to the customer that it is
not your fault but the data provider which didn't respect the specs or
that bugs to happen and that's the way things are.
 
N

Nick Keighley

You said always rethrow your exceptions which means if you follow your
advice there is no difference between asserts and exceptions

exceptions invoke all the DTORs and /they/ could be quite clever.
 
V

Vladimir Jovic

Leigh said:
My choice of language reflects the fact that this newsgroup is rather
informal and is full of posts from idiots.

Not true. Your choice of language shows what your parents thought you.

The fact that the NG is informal and who posts to it is IMO irrelevant.
 
P

peter koch

Ian Collins said:
On 03/15/10 03:03 PM, Daniel T. wrote:
I define [an error] as something the program cannot recover from.
When the design says, "If the user does X, then the program must to
Y," I don't call it an error when the user does X. If the design
requires that condition A must pertain at point B, and condition A
does not pertain at point B; I call *that* an error.
Exceptions are for exceptional circumstances.  If something that
shouldn't fail fails, that's and exceptional circumstance.  That's why
I mentioned file opening failing, if the file should be there, throw
if it isn't.  If it's expected to sometimes not be there, deal with it
on the spot.

The above is essentially what I have been saying all along. How are our
positions different again?

In your belief that an exception should always lead to the termination
of the program and that detetion of a a programming error should lead
to an exeption. This is absolutely silly.
I would add that you only should use the assert macro if you know for a
fact that every program that might ever use the function in question
will find aborting without cleanup an acceptable alternative.

And what kind of program would not want to abort without cleaning up?
In general you would want to do as little as possible after a program
violation has been detected. About the only reasonable thing to do is
asking the backup program to take over, but that should happen
automatically anyway.
The one exeption to this I can think of is game-programming.

/Peter
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,175
Latest member
Vinay Kumar_ Nevatia
Top