Error Code Paradigms

At some point when I was reading the comments on the "Exceptions as repackaged error codes" post, I had an epiphany (it's reflected in the comments to that thread but I wanted to give it more visibility).

I'm sure it's just an indication of just how slow my mind is working these days, but I just realized that in all the "error code" vs. "exception" discussions that seem to go on interminably, there are two UNRELATED issues being discussed.

The first is about error semantics - what information do you hand to the caller about what failed.  The second is about error propogation - how do you report the failure to the caller.

It's critical for any discussion about error handling to keep these two issues separate, because it's really easy to commingle them.  And when you commingle them, you get confusion.

Consider the following example classes (cribbed in part from the previous post):

class Win32WrapperException{    // Returns a handle to the open file.  If an error occurs, it throws an object derived from     // System.Exception that describes the failure.    HANDLE OpenException(LPCWSTR FileName)    {        HANDLE fileHandle;        fileHandle = CreateFile(FileName, xxxx);        if (fileHandle == INVALID_HANDLE_VALUE)        {            throw (System.Exception(String.Format("Error opening {0}: {1}", FileName, GetLastError());        }
    };    // Returns a handle to the open file.  If an error occurs, it throws the Win32 error code that describes the failure.    HANDLE OpenError(LPCWSTR FileName)    {        HANDLE fileHandle;        fileHandle = CreateFile(FileName, xxxx);        if (fileHandle == INVALID_HANDLE_VALUE)        {            throw (GetLastError());        }
    };};class Win32WrapperError{    // Returns either NULL if the file was successfully opened or an object derived from System.Exception on failure.    System.Exception OpenException(LPCWSTR FileName, OUT HANDLE *FileHandle)    {        *FileHandle = CreateFile(FileName, xxxx);        if (*FileHandle == INVALID_HANDLE_VALUE)        {            return new System.Exception(String.Format("Error opening {0}: {1}", FileName, GetLastError()));        }        else        {            return NULL;        }    };    // Returns either NO_ERROR if the file was successfully opened or a Win32 error code describing the failure.    DWORD OpenError(LPCWSTR FileName, OUT HANDLE *FileHandle)    {        *FileHandle = CreateFile(FileName, xxxx);        if (&FileHandle == INVALID_HANDLE_VALUE)        {            return GetLastError();        }        else        {            return NO_ERROR;        }    };};

I fleshed out the example from yesterday and broke it into two classes to more clearly show what I'm talking about.  I have two classes that perform the same operation.  Win32WrapperException is an example of a class that solves the "How do I report a failure to the caller" problem by throwing exceptions.  Win32WrapperError is an example that solves the "How do I report a failure to the caller" problem by returning an error code.

Within each class are two different methods, each of which solves the "What information do I return to the caller" problem - one returns a simple numeric error code, the other returns a structure that describes the error.  I used System.Exception as the error structure, but it could have just as easily been an IErrorInfo class, or any one of a bazillion other ways of reporting errors to callers.

But looking at these examples, it's not clear which is better.  If you believe that reporting errors by exceptions is better than reporting by error codes, is Win32WrapperException::OpenError better than Win32WrapperError::OpenException?  Why? 

If you believe that reporting  errors by error codes is better, then is CWin32WrapperError::OpenError better than CWin32WrapperError::OpenException?  Why?

When you look at the problem in this light (as two unrelated problems), it allows you to look at the "exceptions vs. error codes" debate in a rather different light.  Many (most?) of the arguments that I've read in favor of exceptions as an error propagation mechanism  concentrate on the additional information that the exception carries along with it.  But those arguments ignore the fact that it's totally feasible (and in fact reasonable) to define an error code based system that provides the caller with exactly the same level of information that is provided by exception.

These two problems are equally important when dealing with errors.  The mechanism for error propagation has critical ramifications for all aspects of engineering - choosing one form of error propagation over another can literally alter the fundamental design of a system.

And the error semantic mechanism provides critical information for diagnosability - both for developers and for customers.  Everyone HATES seeing a message box with nothing but "Access Denied" and no additional context.

 

And yes, before people complain, I recognize that none of the common error code returning APIs today provide the same quality of error semantics that System.Exception does as first class information - the error return information is normally hidden in a relatively unsophisticated scalar value.  I'm just saying that if you're going to enter into a discussion of error codes vs. exceptions, from a philosophical point of view, then you need to recognize that there are two related problems that are being discussed, and differentiate between these two. 

In other words, are you advocating exceptions over error codes because you like how they solve the "what information do I return to the caller?" problem, or are you advocating them because you like how they solve the "how do I report errors?" problem?

Similarly, are you denigrating exceptions because you don't like their solution to the "how do I report errors?" problem and ignoring the "what information do I return to the caller?" problem?

Just some food for thought.