error handling best practices

M

MaksimKneller

Under which circumstances is it best to report/record errors via:

- exceptions
- returning an enum indicating success/fail
- just writing the error to a log

So far I can think of 'just writing to a log' as preferable when the
success of a program isn't affected by the error (execution can
continue) but the error is still worthy of note.

So the real question would be - when to prefer exceptions vs returning
a success/fail flag from a function.
 
Ö

Öö Tiib

Under which circumstances is it best to report/record errors via:

- exceptions
- returning an enum indicating success/fail
- just writing the error to a log

So far I can think of 'just writing to a log' as preferable when the
success of a program isn't affected by the error (execution can
continue) but the error is still worthy of note.

So the real question would be - when to prefer exceptions vs returning
a success/fail flag from a function.

Did you read FAQ 17.? It say lot of things you might be interested
in.
 
P

Phlip

Under which circumstances is it best to report/record errors via:

- exceptions
- returning an enum indicating success/fail
- just writing the error to a log
Yes.

So far I can think of 'just writing to a log' as preferable when the
success of a program isn't affected by the error (execution can
continue) but the error is still worthy of note.

And your unit tests should be able to simulate the situation, at the
low level.

(You do _have_ unit tests, don't you?)

A program module should have a boundary and inner logic. The boundary
is where it accepts inputs from the outside "world" - either other
modules, or data, or users. The boundary filters out bad inputs,
converts commands into OO types with the corresponding behaviors, and
then calls into the inner logic.
So the real question would be - when to prefer exceptions vs returning
a success/fail flag from a function.

At the boundary, the calling code expects rejection. It should test a
return value or an "error" field, then route errors into the view for
users to understand.

Some conditions cannot be caught at the boundary, such as a file
abruptly disappearing, or the user entering a combination of inputs
which do not work together, after analysis, or the drive filling up.
Use an exception, generally, if an error's handler appears more than
one call above the error detector. The exception is a special way to
leap from anywhere within the inner logic to the boundary.

Exception handling is a major C++ topic because it's like heavy metal
music - easy to do and hard to do well. At the very least, try to
think about how an exception can cleanly and gracefully roll all state
back until it's the same as when the user entered their last input.
Cancel pending database transactions, un-use and put away all claimed
resources, etc.

Finally, write a log if you have an internal situation that is
survivable, but represents a bug somewhere. And that means actually
_read_ that log! I will get back to you just as soon as I start
following that advice!
 
F

Francesco S. Carta

Under which circumstances is it best to report/record errors via:

- exceptions
- returning an enum indicating success/fail
- just writing the error to a log

So far I can think of 'just writing to a log' as preferable when the
success of a program isn't affected by the error (execution can
continue) but the error is still worthy of note.

So the real question would be - when to prefer exceptions vs returning
a success/fail flag from a function.

When all possible values are valid values you cannot use it as an error,
then you have to use exceptions or add a further argument (a pointer or
a reference) to communicate the error. Another option is to pack the
result along with the flag in the return value (using std::pair, for
instance).

There are lots of different opinions and guidelines about this, you'll
surely get a good amount of replies.

Don't miss to check out the FAQ that Öö pointed out.
 
F

Francesco S. Carta

Finally, write a log if you have an internal situation that is
survivable, but represents a bug somewhere. And that means actually
_read_ that log! I will get back to you just as soon as I start
following that advice!

:)
 
P

Phlip


That was tongue in cheek. I once had a job soak-testing a video game.
The programmers followed my advice, and my soak test would run the
game at random all night, then scrape that log, looking for warnings
and errors. Each morning I would publish a chart showing the ratio of
frames rendered to error messages. This signal trended up over time,
as programmers burned down their bug lists. The trend accurately
predicted when the game would be shippable!
 
P

Phlip

When all possible values are valid values you cannot use it as an error,
then you have to use exceptions or add a further argument (a pointer or
a reference) to communicate the error. Another option is to pack the
result along with the flag in the return value (using std::pair, for
instance).

or you put them in a Fallible template. Given an enum E, which
contains all legitimate returns, your method looks like this:

Fallible<E> func();

If the E is invalid, attempts to use the E inside the Fallible raise
an exception. The Fallible provides is_valid(), so you can use an if
instead of an exception. IIRC I once wrote a Fallible that also
contained an error message explaining while it failed.

Fallible is from Barton & Nackman's book on scientific & engineering C+
+.
 
F

Francesco S. Carta

That was tongue in cheek. I once had a job soak-testing a video game.
The programmers followed my advice, and my soak test would run the
game at random all night, then scrape that log, looking for warnings
and errors. Each morning I would publish a chart showing the ratio of
frames rendered to error messages. This signal trended up over time,
as programmers burned down their bug lists. The trend accurately
predicted when the game would be shippable!

That's very nice :)
 
F

Francesco S. Carta

or you put them in a Fallible template. Given an enum E, which
contains all legitimate returns, your method looks like this:

Fallible<E> func();

If the E is invalid, attempts to use the E inside the Fallible raise
an exception. The Fallible provides is_valid(), so you can use an if
instead of an exception. IIRC I once wrote a Fallible that also
contained an error message explaining while it failed.

Fallible is from Barton& Nackman's book on scientific& engineering C+
+.

Very nice!
 
Ö

Öö Tiib

or you put them in a Fallible template. Given an enum E, which
contains all legitimate returns, your method looks like this:

   Fallible<E> func();

If the E is invalid, attempts to use the E inside the Fallible raise
an exception. The Fallible provides is_valid(), so you can use an if
instead of an exception. IIRC I once wrote a Fallible that also
contained an error message explaining while it failed.

Fallible is from Barton & Nackman's book on scientific & engineering C+
+.

I have used boost::eek:ptional on cases similar with Fallible. Some
functions may have nothing to return but it might be too commonly
expected to call it an error and raise (expensive) exception.
 
J

James Kanze


There are even a few rare cases where the best solution is
simply to memorize the error in the interal state of an object,
for later testing, a la iostream. (And when I say rare, I mean
very rare.)

And of course, there's also the possibility of aborting with an
error message.
And your unit tests should be able to simulate the situation, at the
low level.
(You do _have_ unit tests, don't you?)
A program module should have a boundary and inner logic. The
boundary is where it accepts inputs from the outside "world"
- either other modules, or data, or users. The boundary
filters out bad inputs, converts commands into OO types with
the corresponding behaviors, and then calls into the inner
logic.

If the calling function violates any part of the contract
(typically pre-conditions), then the only correct handling is an
assertion failure---abort with an error message. The program is
broken.

(For the rest, I pretty much agree with you.)
 
J

James Kanze

When all possible values are valid values you cannot use it as
an error, then you have to use exceptions or add a further
argument (a pointer or a reference) to communicate the error.
Another option is to pack the result along with the flag in
the return value (using std::pair, for instance).

You do not use std::pair for this. The names of the elements
are wrong, and it doesn't have the desired syntax. There have
been several variants proposed at different times: the oldest
(and most established?) is Fallible, from Barton and Nackman,
but Boost has optional (whose name misleads for this use), and
I've also seen Maybe (whose name probably engages the least,
since it doesn't say anything about why the value might not be
present: failure, because it's not required, or because it is
a cached value which isn't up to date).

The salient points are:
-- the class has an isValid() method, which returns true if the
value is valid, and false otherwise, and
-- accessing the value when isValid() returns false is
a violation of the contract, and results in an assertion
failure.
(In the case of Fallible, it's often interesting for the object
to be able to carry an error code as well.)
 
F

Francesco S. Carta

You do not use std::pair for this. The names of the elements
are wrong, and it doesn't have the desired syntax. There have
been several variants proposed at different times: the oldest
(and most established?) is Fallible, from Barton and Nackman,
but Boost has optional (whose name misleads for this use), and
I've also seen Maybe (whose name probably engages the least,
since it doesn't say anything about why the value might not be
present: failure, because it's not required, or because it is
a cached value which isn't up to date).

The salient points are:
-- the class has an isValid() method, which returns true if the
value is valid, and false otherwise, and
-- accessing the value when isValid() returns false is
a violation of the contract, and results in an assertion
failure.
(In the case of Fallible, it's often interesting for the object
to be able to carry an error code as well.)

The std::pair was just an (admittedly unhappy) example of aggregated
return. Thanks for pointing out better alternatives.
 
J

James Kanze

or you put them in a Fallible template. Given an enum E, which
contains all legitimate returns, your method looks like this:
Fallible<E> func();
If the E is invalid, attempts to use the E inside the Fallible raise
an exception. The Fallible provides is_valid(), so you can use an if
instead of an exception. IIRC I once wrote a Fallible that also
contained an error message explaining while it failed.
Fallible is from Barton & Nackman's book on scientific
& engineering C++.

That's my reference as well. My current version has been
extended to support types with no default constructor, and to
support extended error codes (with a second template argument).
The implementation was available at my site, but since my site
doesn't currently work, that's not much good to anyone.
 
R

RB

(note exception handling for SEH and C++, are compiler implemented
(on top of win32's chained frames) so check your compiler version's
docs for specifics. For running on XP targets you also have vectored
exception handling api options.)
Additionally I myself am a novice programmer and not a professional.
But I have studied exceptions a great deal more than someone of my
competence level would normally have.
I would say for immediate area code, error handling is preferable.
But for areas that must traverse from a start through other code
before finishing the process, "then" exception handling is
preferrable since if done correctly it will destruct any objects
created in said area of code and additionally give you a vehicle
to implement cleanup of as you back out of a failed situation,
like if a file does not open for whatever reasons. You must still
either put deletion code (of allocated variables) into the destructor
or else write deletion code in the pertinant places as you back
out of exception frame. With MFC or some third party libs this has
already been done for you in many areas.
Another area where exception handling (at least SEH exceptions)
is good, is for crash reporting. You can put a __try and __except
into you main function to cover all hardware divide by zero and
illegal access (or any other uncaught exception) and then write a filter
function to report stats to a file on crash as in,
__except( YourFilter( GetExceptionCode( ), GetExceptionInformation( ) ) )
in YourFilter, after your file reporting code, if you
return EXCEPTION_CONTINUE_SEARCH;
then when you app exits the OS crash dialog will come up asking if user
wants to send (the OS final handler's) report to MS. However if you
return EXCEPTION_EXECUTE_HANDLER; then the SEH handler
exit your app with no OS crash dialog (and you could write your own,
to call from filter function possibly to email "you" instead of MS)
Then for this to work, in all object functions you call out of main( )
you would need to implement c++ try block inside each of them and have
a lone catch(...) block ( for each try ) to catch any exceptions you have not
specifically catch with a, catch(YourExceptionObj& E) . Then you
would also need to rethrow all (...)caught exceptions (with a "throw" ) if
you want them to make it back to the main( )'s __except block. So
obviously you would want this only for crash report writing and would
not rethrow any specific exceptions you choose to handle without need
of reporting. On most later version compilers you have to compile with a
special switch to get SEH capability so check your docs.
How much you output to file and how much user system info
you collect is a wide spectrum that can go from simple to complex.
Addtionally (depending on how much code you are covering in try blocks)
doing such will increase the size of you app (for the unwinding code the
compiler implements) You could #ifndef out the E blocks and compile
each way to see how much size increase is occurring.
Good luck with it if that is what you are after.
 
P

Phlip

If the calling function violates any part of the contract
(typically pre-conditions), then the only correct handling is an
assertion failure---abort with an error message.  The program is
broken.

You shift the question when to throw into the question what's a
contract.

If the contract says "the user might try to screw you up, and you must
return a valid error message", then the contract says return, not
throw.

The point of throwing is getting out of arbitrarily deep logic, if it
detects an error that didn't exist when (or was undetectable when) the
logic started.

Other than that, I agree with your agreement.
 
P

Phlip

The std::pair was just an (admittedly unhappy) example of aggregated
return. Thanks for pointing out better alternatives.

someone was jonesin for an interpretive language that returns tuples:

return (a, is_valid)
 
F

Francesco S. Carta

someone was jonesin for an interpretive language that returns tuples:

return (a, is_valid)

I'm not sure how I should interpret your sentence - I also needed to
find "jonesin" in the urban dictionary.

Are you implying that I could be accustomed to such languages and that I
threw std::pair in for that reason?

In such case, you'd be wrong. I'm actually not aware of any language
that permits such a syntax, it might be allowed by the interpreted
languages that I occasionally use but I simply ignore such fact.
 
J

James Kanze

I'm not sure how I should interpret your sentence - I also needed to
find "jonesin" in the urban dictionary.
Are you implying that I could be accustomed to such languages and that I
threw std::pair in for that reason?
In such case, you'd be wrong. I'm actually not aware of any language
that permits such a syntax, it might be allowed by the interpreted
languages that I occasionally use but I simply ignore such fact.

http://golang.org/doc/go_spec.html#Return_statements

Google's answer to Java and C#? (If so, they've done a lot
better.)

More generally, if I were designing a language (and in the one
language I did design), it would certainly be possible to return
more than one value from a function. And assign more than one
value in an assignment statement---swap should be simply:
x, y = y, x;
In the case of multiple returns, the function should be declared
as returning such, and each of the returns should have a name
(not necessarily first and second).
 
A

Alf P. Steinbach /Usenet

* James Kanze, on 24.08.2010 17:34:
http://golang.org/doc/go_spec.html#Return_statements

Google's answer to Java and C#? (If so, they've done a lot
better.)

As I understand it's Google's answer to Python. They intended to replace their
use of sloooooooow but convenient Python with fAst but little less convenient
Go. BTW., Python also supplies tuples and the exact return stm syntax shown.

More generally, if I were designing a language (and in the one
language I did design), it would certainly be possible to return
more than one value from a function. And assign more than one
value in an assignment statement---swap should be simply:
x, y = y, x;

Huh, that's also valid Python syntax.

In the case of multiple returns, the function should be declared
as returning such, and each of the returns should have a name
(not necessarily first and second).

Nah, no type declarations in Python, it's all dynamically typed.

Curiously there's syntax for it, much like Pascal, but what that syntax does is
just to produce documentation strings. Silly.


Cheers & hth.,

- Alf
 

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,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top