Error handling

Tags: programming errors bestpractice

Error handling can be difficult to do right. There are two primary error models in most prominent programming languages: error codes and exceptions. Further, a distinction must be made between recoverable and non-recoverable errors.

Error Codes

Error codes are what you see in a programming language like C or Go. Every function that may encounter an error can return an error status in its return value. C does this through integers where typically a 0 represents no error and anything else means an error occurred. Go uses explicit error objects.

Error codes are straightforward and easy to understand, but they have a few important flaws. The biggest problem is the fact that there is no enforcement that the error code will in fact be checked. That falls upon the user, and all too often this important practice is neglected.

The other problem is that returning an error code collides with the need for functions to return an actual value. In Go, this is solved by allowing functions to return multiple values (typically a "result" and an optional error). In C, this can be worked around by adding the outputs of a function as an input pointer parameter:

int sum = 0;
int err = calc_sum(a, b, &sum);
if (err) {
    // Handle error
}

This works, but can be potentially clunky and awkward.

Exceptions

The other major error handling model is exceptions. This is what languages like C++ and Python use. Exceptions handle errors by propagating the error up through the call stack until it is handled in a catch or try. If it never gets handled, it aborts and crashes the program.

Exceptions are common in object oriented languages (like C++ and Go) because you cannot return an error code in an object's constructor. Exceptions have problems too, however. Most notably is the fact that it makes programs, in a sense, "non linear". An exception can occur at any time and, in the case that it is handled in an exception handler, it can be hard to know when the exception occurred and what actually caused the exception. This tends to be more of an issue with low-level systems programming than in application programming.

Exceptions create hidden control flow and corrupted state. Any statement can potentially throw an exception, which can make it very difficult to reason about how a block of code actually functions.

Recoverable vs. Non-Recoverable Errors

The distinction between a recoverable and non-recoverable error is highly context and application dependent. It is therefore difficult to try and prescribe well-defined rules for what type of error is which. However they're defined, non-recoverable errors should "quit and get out fast": Rust and Go have a "panic" feature that does this, and C has abort().

Further Reading