Error Codes, Exceptions, and More

John Meschke | 2019-02-05

Background

In modern languages like Java, Python, and C++, the ability to throw and catch exceptions in the presence of a significant error has become standard practice. Prior to having these features and languages available, error handling was generally performed by returning an error code from a function or by populating a variable to hold the result of an error.

In both of these circumstances, the program can be designed for error recovery. There are a hundred different ways to do the same job when it comes to programming, and error handling is no different. Generally speaking though, we usually throw an exception or give feedback as an error code. There have been many heated debates on whether or not exceptions are a blessing or a burden in comparison to error codes.

Other ways of tackling errors are less talked about and tend to be dependent on the practices of the development team. Sometimes errors are just completely ignored. Whatever the case may be, understanding and tackling various forms of errors in a program is a necessary skill.

Exceptions

One major advantage of using exceptions is that they can't be ignored. If an error has occurred, it must be fixed in the code or there must be some fallback mechanism to recover. Whether or not to recover from an exception largely comes down to desired behavior of the system.

A recoverable exception might be caused by a missing texture file for a game asset. In this case, we simply catch the exception and replace the requested texture with some default texture. It might look awkward in the game, but it won't bring the entire program to a halt.

An unrecoverable exception might be caused by lack of memory. If this point is reached, there is very little that can be done to continue. Terminate the program and let the user know about it. Maybe they need more RAM or maybe they should adjust their settings to prevent the program from hogging resources.

The major advantage of exceptions is that it makes code much easier to read. You don't have several branching paths to handle errors in a group of function calls, just throw everything in a try block and catch any errors that happen.

public void createDisplay(System system)
{
    try
    {
        system.activateWindow();
        system.setWindowSize(WIDTH, HEIGHT);
        system.displayWindow()
    }
    catch (DisplayException e)
    {
        Log.error("Screen creation failed: " + e);
    }
}

Another advantage to exceptions is that they are the only way to correctly handle an error in a constructor. If an object cannot be constructed, it should not be used. Without exceptions, the object would be left in an invalid state. If the programmer didn't bother to check the state of the object before using it, the results could be disastrous.

An argument against exceptions is that they don't follow the flow of code, sending the current process into some far off land when the error occurs. With return codes, you can see exactly where a failed function call will take you. With exceptions it isn't always clear on where you'll end up. An exception that goes uncaught will propagate through the callers until it's caught or the program terminates.

Error Codes

Error codes have the advantage of simplicity and clear direction of code flow. If a function call succeeds, it's perfectly clear as to where the code execution continues. Beyond that, the only other advantage is compatibility with code bases that don't support exceptions. For example, writing a library that can be used by both C and C++ languages.

A lot of the times, programmers just don't care why a function call failed or if it even failed at all. I'm guilty of this too. I feel like a lot of the libraries I've worked with over-report errors, especially when the result wouldn't make much of a difference. The disadvantage of error codes becomes apparent when you realize the caller doesn't need to obey the given warning.

public void createDisplay(System system)
{
    // All these return a result, but whatever.
    system.activateWindow();
    system.setWindowSize(WIDTH, HEIGHT);
    system.displayWindow()
}

Error codes aren't completely obsolete in todays exception-handling languages though. Some financial software systems absolutely require that the system can never go down. An uncaught exception will bring the system to its knees. In such a system, extreme precaution is taken to ban all uses of exceptions. Returning an error code may be the only option, but the programmers must be diligent in following the standards of their code base.

Result or Status Codes

I feel the need to make a distinction between error codes and result or status codes. With an error code, you have a reason as to why a function failed. With a status code, you learn about the results of the function operation.

The strcmp() function in C++ returns one of three integer values, letting the user know if the compared strings are shorter, longer, or equal. This is a perfectly reasonable example of a status code. It wouldn't make sense for the user to ignore the return value, but it won't cause a problem if they do.

Limiting Error Handling

Not every error that happens needs to be resolved. After adding status codes as return values from just about every method in my code base, I quickly found my way into the camp of the programmers mentioned earlier. I just didn't check if the method failed or not. There was no reason to do so.

In my sprite drawing utility class, the instantiated object stores a maximum number of sprites it can buffer before sorting and drawing them to the screen every frame. If the number of sprites to draw this frame exceeds the maximum capacity, then nothing happens.

public void addSprite(TextureData data, Rectangle2D position)
{
    if (sprintCount >= MAX_SPRITES) return;

    buffer(data, position, spriteCount);
    spriteCount++;
}

Technically this is an error, but does it really matter? It's not a catastrophic problem, some items on the screen just won't get displayed. If this anomaly is encountered during testing, the maximum sprite capacity can be increased.

Considering the number of places this method could be called, would it really be reasonable to expect every caller to check for an error code? Not really. Would an exception be overkill? Probably. What would the caller even be able to do anything about it? No. The most reasonable thing to do here is write to an error log in the debug version of the code. That way any potential problems can be found and corrected quickly.

Default Behaviors

Sometimes we just want the program to work with whatever we give it. For example, you might pass a negative value to a constructor specifying the initial size of a container. The constructor may throw an exception or it may increase the initial size to an acceptable value. The latter behavior is shown here.

public Container(int initialCapacity)
{
    int size = Math.max(0, initialCapacity);
    items = new Item[size];
}

This makes sense if you want to make the program extremely robust in a release build. The downside of this style of error prevention is that you could be hiding problems inherent in the program. Perhaps an invalid object could propagate its way through the system undetected. This could make debugging much more difficult in the long run.

My stance is to catch errors early and catch them often. You're weeding out the bugs before they get a chance to grow into something much less manageable.

Conclusion

I tend to agree with the pro-exception crowd for major errors. The code is more readable without branching paths all over the place. In my personal game libraries, exceptions reign supreme. I have never really run into any situations in which error codes made any sense. For minor errors with little or no consequence, the result is a do-nothing operation. It keeps the code simple in terms of readability and doesn't pass any responsibility to the callers.