Octopull/Java: If Problems Arise
Home C++ articles Java articles Agile articles Bridge lessons

If Problems Arise by Alan Griffiths

Every so often I come across the question "should we use return codes or exceptions to report errors". It is usually the case that this is asked with the expectation that the answer is either "return codes" or "exceptions" - even to the extent that after I explain that this is not so my advice is "simplified" to one of the above. The correct answer requires a close examination of the assumptions of the questioner. There are several significant variations on both return codes and exceptions and there are further ways to report problems that don't fit under either banner.

In this article I'm going to provide a checklist of mechanisms for reporting problems found in the C, C++ and Java languages - it is left to the interested reader to decide which of the many options is the one true way sought by my hypothetical questioner.

For the sake of the following discussion suppose that I am specifying an API function that will normally produce some desired effect but that will sometimes encounter a problem and need instead to report on that problem. The following discussion describes a range of designs that provide that reporting mechanism.

We start with return codes - the principle difference between these designs is in the way in which the code is communicated to the calling code:

MainBandReturnCodes

In this case the function return value is always and only a return code to identify any problem. (If the function also needs to return a result then I need to consider another option.)

InBandReturnCodes

This uses part of the range of possible return values for results (e.g. positive values) and part of the range for error codes (e.g. negative values). A typical example of this is " malloc " from the C standard library (or the " nothrow " variant of new in C++).

SideBandReturnCodes

In this case the function returns a composite value that incorporates the both the return code and the result value. The author of the calling code is then responsible for checking for an OK return code before using the result.

CheckedReturnCodes

an elaboration of SideBandReturnCodes that has been implemented in C++ is one in which accessing the result without first checking the status causes either an exception to thrown (or sometimes " assert " or " exit " to be called).

Closely related to return codes are status variables. These have the potential advantage over return codes that the client code may call several of these methods before checking the status variable - although it needs to check the status before using the results. This can allow a convenient separation of the principle logic from the error handling. However, this flexibility is not always as useful as it sounds - unless the API functions are sufficiently robust (have defined behaviour) following the first failure the error must still be checked for and handled after each call by the client code.

GlobalStatusVariable

I guess the most well known example of this is the C standard library " errno ". This is a variable that may be set by many of the library functions if they are unable to achieve their primary goal. (Note that none of them clear it on success - clearing " errno " is left to the client code.)

UserScopedStatusVariable

In this variant the calling code passes a reference (or pointer) to a status variable into the API function and if a problem occurs then this variable is updated in much the same way as in GlobalStatusVariable .

ObjectScopedStatusVariable

When the API function is a member-function of an object it is possible for the status variable to be held on the object instance. The C++ streams template classes use this approach.

Moving on there are mechanisms for altering the program flow - setjmp/longjmp and exceptions. These have the big advantages of separating the normal processing from error handling but in C and Java can make it difficult to guarantee that paired operations (such as allocating and freeing resources) are managed correctly.

setjmp/longjmp

In C (and theoretically in C++) longjmp returns the flow of control to a point noted by a previous call to setjmp . This is rarely appropriate for an API because of the resource management issues it creates for the client code.

CheckedExceptions

These only exist in Java where they have the effect that the calling code must make explicit provision for their being thrown (or the compiler rejects the code). This limits the placement of error handling code - the exception can only propagate through methods that make explicit provision for it. These exceptions are most useful where the method containing the calling code will be able to handle the error condition - for example when opening a file fails.

UncheckedExceptions

The remaining category of Java exceptions and all C++ exceptions can propagate without explicit provision. These are best reserved for serious issues that require any current activity to be aborted - such as running out of heap space. (I discuss the use of Java unchecked exceptions in more detail in Overload 48.)

All of the above mechanisms have one thing in common - the API function I'm designing will terminate as a result of the problem. However, this can be avoided:

CallbackNotification

The calling code may provide a function to be called in the event of problems (either as a function pointer or by supplying an implementation of a suitable interface). This provides the opportunity to continue processing if something can be done about the problem. Since the possibility returning from the API call if the problem can't be resolved must be allowed for it does complicate the design - and, in practice, seems to be less useful than it sounds.

ResumableExceptions

Although these are not a standard part of any of the languages they do crop up as extensions. They combine the mechanism of finding an exception handler up the call stack with the possibility of the handler "fixing" the problem and allowing processing of the API call to continue.

Any of the above may be appropriate according to the needs of the particular API function, but this isn't simple advice - and I haven't come up with a simple decision mechanism that gives good results.