views:

895

answers:

10

I'm working on a library that generates reports of devices. The generate_report (const std::string& no) member function can fail due to various reasons:

  1. invalid report no.
  2. invalid state (the report_generator is a FSM)
  3. no device is active
  4. error during report generation

Which error-handling mechanism is best for these errors?

  • just return true or false
  • return error code
  • assert and log
  • throw exception(s)
  • any combination of the above

Some context information: the normal workflow is as following. The user activates a devices, chooses a report from a list and clicks on "generate".

EDIT: Thanks for the replies so far! For me it's clear now when to use asserts and when to do error-handling. As for error-handling, error codes and exceptions both have pros and cons. I think I go for exceptions (and create four classes for the above errors), but I'm not yet really convinced. I always thought of exceptions of 'unexpected situations'. An invalid report no isn't really unexpected. Any advice? :)

+1  A: 

It is often a matter of taste what strategy to choose. I say pick up what best integrates with the clients of your library. If they adopt exception strategy, use exceptions. If they are accustomed to error codes, stick with it.

Developer Art
+3  A: 

assert is not the right choice. Use assert when you have an invariant; something that should never happen. Don't do things like assert() that an argument will never be null if it is an error condition and not an invariant.

If it were me, I would use exceptions in the interface and, if I had to, translate error codes by functions used internally if they do not use exceptions. Just be consistent about it (and don't use assert for this stuff).

Ed Swangren
A pointer returned by new will not be null, unless you use "new (nothrow)".
Daniel Daranas
You sure about that? I know that the 'standard' states that new will throw an exception if it fails, but there is a lot of legacy code out there that was written well before that rule was made. Anyhow, perhaps it was a confusing example in this case as it won't apply in most cases.
Ed Swangren
@Ed Yes, I am sure. Unless you use a vendor-specific compiler flag that says "new never throws", the legacy code which checked if new returned null will actually throw when new fails (which, btw, for normal news, is "never"). Hovever, I do agree with your main point.
Daniel Daranas
Yeah, "are you sure about that" was wrong because on any modern compiler new will throw instead of returning null. Changed the example.
Ed Swangren
A: 

First - be consistent!

Second:

  • just true/false is not enough. It has to be combined with error codes (false + getLastError for example).
  • error-codes are fast, but build some infrastructure to convert them easily into strings.
  • assert/log: no, you want the application to be able to react on the error
  • exceptions are slower than error-codes, but easier to program with a difficult control flow.
  • combinations: only true/false + error codes combine, for the rest BE CONSISTENT which means: do not combine.
Tobias Langner
A: 

Why not use the most sensible one for you - say exceptions. And then supply a set of decorators libraries that just provide the others as samples for them to use.

This way you support only one but give them extra help is using you're method?

Preet Sangha
+1  A: 

I recommend reading the Boost community guide [boost.org] to exceptions and error handling.

Vladimir
+3  A: 

Any of these have different purposes:

  • error code vers. exception(s): exceptions and errorcodes represent different idioms of how result codes shout be handled. Exceptions are more robust - resultcodes can be ignored or lost. A library should usually strongly distinguish where/what exceptions are thrown, and when error codes are used. At best, use only one of both at all.

  • return true or false: A specialization of error codes. Usually the worst idea - only good if there is no more to report than good or bad (i.e. alloc returns either good or bad (= NULL).

  • assert and log: These are debugging techniques, and should not be used as report mechanisms to users / clients. Asserts just say "something happened, that i can not handle - i quit".

RED SOFT ADAIR
The behavior of assert (on false) is not necessarily to quit. It can also be to log to a log file with information like the source file name, line number, a stack dump and the assert text (that may contain values of some variables). Another behavior is the default behavior for Windows applications created with Visual Studio: a dialog box is shown with buttons to abort, retry or ignore.
Peter Mortensen
Yes, a assert does not have to quit - but thats the typical case. I use my own asserts that offer exactly the possibilities you mention(continue, quit etc.). By my experience though in most cases there is no safe way out as errors are repeated continuously.
RED SOFT ADAIR
A: 
  • logging should be used if you don't have access to a terminal for producing/reading error reports.
  • returning True/False should be combined with error codes. Example: A function returns True on success, False on error, and sets a variable (global or parameter, your choice) with an appropriate error code/description.
  • exceptions: in my opinion, it's good to combine them with logging and graceful recovery from errors. If this is not possible, you might as well resort to error codes, as exceptions then provide no additional benefits.
  • assert(): As others have pointed out it compiles away on release builds, so fire at will.

2c

Michael Foukarakis
Er, doesn't assert() compile away in Release builds?
JBRWilkinson
That is true in the case of MFC macros, but not in the general case.
Michael Foukarakis
You might want to lookup the documenation. assert() from <cassert> does compile away in release builds.
MSalters
+1  A: 

How reliable are the devices you are reporting on?

I ask because for a large class of devices not connected, not switched on, out of batteries, busy doing something else etc. etc. are fairly normal states.

If this is the case I would favour returning a status code (note not an error code ) if the device is somehow unavailable.

If on the other hand you consider these devices super reliable and it really is exceptional for them not to respond then exception handling may be the way to go.

It doesn't really matter that mutch as 'exceptions' are really just a fancy way to code 'if (x != 0) { goto error_routine; }, but, I personally prefer exception handling to deal with exceptional situations not routine events like end_of_file.

James Anderson
Good point. I think the question is what's an exceptional situation. E.g. if I want to generate report no 42 which doesn't exist, or generate a report while no device is selected.
+2  A: 

Exceptions compared to true/false and error codes have several important advantages:

  • Exceptions cannot be ignored. If your code throws an exception the caller has to catch it to avoid getting an unhandled exception.
  • Exceptions can be handled at a higher level than the immediate caller. If you use error codes you may end up in situations where you at all layers of you application have to check for errors and pass them back to the caller.

Asserts are used to express stuff like preconditions in your code and will hopefully uncover any bugs during development. However, you should not rely on asserts in your release code, and for performance reasons asserts are normally removed from release code.

Martin Liversage
The danger of exceptions in C++ is that you can end up with hard-to-track memory leaks by leaving a stack frame that frees memory.
carl
A: 

I'm going to go against the grain and suggest both error codes and exceptions, but only because you are making a library. Since you say you are making a library, I'm guessing that library will be made available to code written by people you have no control over. So, making your code friendly to different compilers and possibly even languages is a good thing.

So I would code a C++ exception library and provide header files detailing your exception classes. I would also code a C interface that handles the exceptions for the user. Now the user can link against which ever interface is appropriate:

#ifdef __cplusplus__
void generate_report(const std::string& rep_number, ostream& output);

extern "C" 
#endif
int generate_report(const char* rep_number, const char* outputfilename,
                    int* error_code, char* error_text, int max_error_text_len);

The C implementation calls the C++ implementation:

extern "C" 
int generate_report(const char* rep_number, const char* outputfilename,
                    int* error_code, char* error_text, int max_error_text_len)
{
    ofstream os;
    try {
        os.open(outputfilename, IOS_WRITE);
        generate_report(rep_number, os);
        os.close();
        return TRUE;
    } catch (base_exception& e) {
        os.close();
        if (error_code) *error_code = e.error_code();
        if (error_text) strncpy(error_text, e.str(), max_error_text_len);
        return FALSE;
    }
}
jmucchiello