Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

Error Code Paradigms

Error Code Paradigms

Rate This
  • Comments 33

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.

  • In many ways I was unknowingly dancing around this very observation with my post about the difference between throwing the return code and throwing the return code's string representation. My view was from the consumer standpoint which was more about what was being reported and not how it was being reported. It wasn't until your post that I realized this myself.
  • With the new longhorn system being more .Net based hopefully we'll see more innerExceptions describing exactly what the real reason was 5 levels down in the API.
  • What you say is correct, but it's not the only separation of errors that should be considered.
    There are two types of fault - those that are expected and those that aren't.
    Expected errors will be handled (whether by exception or error code) close to the point of the call that failed.
    Unexpected errors have to return the system to a stable state and then get ready to try again - how far back they have to go in order to do this depends on nature of the application and the fault (it could be a network server that drops this request and readies itself for the next, or it could be a Windows app that kills the process and allows the user to restart it).

    The key thing IMHO is that it is not the OpenFile function that should be making the choice - it is the caller that should.
    More specifically, a library should never make the choice.
  • I agree with much of what Yaytay said. My first experiance with exceptions was with MFC's CFile class that just loved to throw exceptions in the case of opening files. In my application, file open errors are expected. Thus trying to use the exception system made the software much harder to write than using just simple fopen.
  • Yaytay, IMHO, that's a valid point but unrelated to my discussion.

    The problem (as you mention) is that it's impossible for a DLL (or other library) to know what the intentions of the caller are. All it can do is to specify its contract and push the problem up to the caller.
  • Even compared to "rich" error returns, throwing exceptions gives you more (debugging) information - namely the stack traceback.

    If you return your error information, the information about the original error location is usually lost. And, if you return the same error object further up the call stack, the the call stack is lost.

    On the other hand, Exceptions automatically maintain a stack traceback that can be reported at the catching site for debugging purposes.

    Of course, you could also maintain a stack traceback with error returns, but you would have to maintain it manually, which is clumsy and error-prone.
  • oefe: How do you get the stack traceback?

    Exceptions don't "automatically" maintain a stack trace unless you have the debugger break when someone throws an exception?

    How do you differentiate between the real failure and the false positives? Because if the system's using exceptions exclusively you're likely to have false positives (I know, I've been burned by this before).

    I think you're once again confusing a property of System.Exception (stack backtraces generated at the point of object construction) and a property of exception handling as a propogation paradigm.
  • Just because everyone hates to see "access denied" with no context does not make it a bad thing.

    Error messages can easily report too much information to a caller, particularly if the caller is hostile. I would much rather my web server report "access denied" to a malicious hacker than "access denied to SQL server database foo on machine bar, ip address blah, while executing query abc".

    My favourite example of this, which fortunately was fixed long before .NET v1.0 shipped to customers, was an exception message that basically said "you do not have permission to determine the name of directory "c:\blah\whatever" " -- great, thanks for letting me know!

    My least favourite example of this, because it was my fault, was a serious security hole in VBScript -- http://blogs.msdn.com/ericlippert/archive/2003/09/24/53089.aspx
  • You're absolutely right Eric - Sometimes it's NOT good to return the full context that's contained in the exception, especially if there's a bad guy on the other side.

    On the other hand, from a usability expert, the "Access is Denied." message box ranks well up there in the "messages that suck" department.
  • Good post, but I think it's a bit odd to say "I recognize that none of the common error code returning APIs today provide the same quality of error semantics that System.Exception does". You've essentially staked out a middle ground that exists only in theory. Nobody who advocates for return codes over exceptions offers this as an alternative.
    I'm not denying it's a vast improvement over returning an int - it is - but there must be some reason you never see this in practice. Is there a practical downside that makes this less attractive than it seems? There's some sort of runtime penalty (in C, at least).
  • Throwing exceptions is a fun and wonderful way to write code. It's catching them that sucks. Give me error codes over try/catch any day.
  • Throwing exceptions is a much better way to code. Catching them is what sucks. Gimme error codes over try/catch any day.
  • I think the point is propagation of exceptions, and the fact that if the developer doesn't catch them or handle them in some way, he'll see it anyway(at runtime :-) ??? ). The code doesn't continue executing.
    Having a system with error code, no matter how descriptive, it's totaly up to the developer to investigate the error code for success or failure. In this case the code keeps executing and this can lead to some major bugs which may not show themselfs right away.
  • Eric: Using a counter example where too much information was leaked is _not_ a justification to build APIs that report genercic error messages. I think this is one pervert effect of the security push inside ms. We end up finding excuses for sloppy error reporting for the sake of "security". The matter of fact is that "Access is denied" (actually, my scapegoat is usually E_FAIL: Unspecified Error) is not a useful error message. ASP.NET implemented a great workaround to the information leakage problem: Exception messages aren't sent back to the client browser by default. That's a much more imaginative solution than saying "Report generic error message, so an eventual attacker can't do anything with it". If you're not convinced, let me use a counter counter example: just think what our life as programmers would be if our favorite c++/c# compiler would merely report "Invalid type" when compiling a program containing a type mismatch error: no filename, no line #, no type name. Justification: The compiler error reporting mechanism had to be trimmed down for security reasons because we didn't want attackers to know the name/location of our source files. Every programmer in the world would say this is absurd. But it seems we have no problem putting in place a similar system for end users.

    Larry: I agree there is a difference between what error is returned and how it is returned. I also agree error codes/exceptions can be equally used (and equally misused) for both.

    In general, however, the perversion of the discussions I've seen about exceptions vs error codes is that, even if they are considered equivalent in "theory", they are compared against their current/actual implementations. For example, I've heard many times "throwing an exception is more expensive than returning an error code". Of course, that is correct if an error code is a DWORD, but if you set up a system where error codes are rich, well, returning an error code is a matter of returning the equivalent of System.Exception. Is that really less expensive? (Probably a little bit, but certainly not orders of magnitude as we've been told before).

    I have a personal preference for an exception based mecanism, because the intent is often more explicit. In your "OpenException" example, it's not cleat what the value of "FileHandle" is supposed to be when the return value is not null. You annotate it with an "OUT" modifier, but it's purely documentation. We've all run into bugs where we don't initialize the return value when we return S_OK or where the caller expects us to set the output param to a "resonable" default value even when the function fails. An exception based system eliminates this class of bugs. I think it also tend to make code cleaner (no noise about bubling up error codes to the callers), and it forces programmers to adopt sound programming practices (RAII in C++: no goto's!, try/finally in c#).

    That said, I am always surprised how much time spent is arguing about exception vs error codes, and i have yet to see any improvement in API's published by MS wrt to rich error reporting. Most API's (I suspect Longhorn will not be much different, now that managed code usage has been reduced) are HRESULT based, and don't even bother supporing IErrorInfo. If I had the choice between a rich error reporting mechanism vs an poorly designed exception based one, i would chose the former one any day.

    Could we for once start a discussion about defining a rich error reporting mechanism (exception based or not) that could be used and exposed by Windows APIs. That would benefit customers much more than philosophical discussions.

    - How do we support localization for a rich error reporting mechanism?
    - How do we make sure programs don't take dependencies on message strings?
    - How do we make sure it work accross APIS (Win32, COM, etc.)?
    - How do we extend existing APIs to suport the new model?
  • I have to agree with Andrei. Coming from a Delphi background, exceptions feel very natural to me.
    It takes the same amount of work to differentiate between expected and unexpected exceptions - reading the docs or code - but at least you can be sure you'll be stopped right in the place where you failed. The RTL makes sure you do.
    Not something you can say for error codes.
Page 1 of 3 (33 items) 123