Conventions for exceptions or error codes

Language AgnosticLanguage Design

Language Agnostic Problem Overview


Yesterday I was having a heated debate with a coworker on what would be the preferred error reporting method. Mainly we were discussing the usage of exceptions or error codes for reporting errors between application layers or modules.

What rules do you use to decide if you throw exceptions or return error codes for error reporting?

Language Agnostic Solutions


Solution 1 - Language Agnostic

In high-level stuff, exceptions; in low-level stuff, error codes.

The default behaviour of an exception is to unwind the stack and stop the program, if I'm writing a script an and I go for a key that's not in a dictionary it's probably an error, and I want the program to halt and let me know all about that.

If, however, I'm writing a piece of code which I must know the behaviour of in every possible situation, then I want error codes. Otherwise I have to know every exception that can be thrown by every line in my function to know what it will do (Read The Exception That Grounded an Airline to get an idea of how tricky this is). It's tedious and hard to write code that reacts appropriately to every situation (including the unhappy ones), but that's because writing error-free code is tedious and hard, not because you're passing error codes.

Both Raymond Chen and Joel have made some eloquent arguments against using exceptions for everything.

Solution 2 - Language Agnostic

I normally prefer exceptions, because they have more contextual information and can convey (when properly used) the error to the programmer in a clearer fashion.

On the other hand, error codes are more lightweight than exceptions but are harder to maintain. Error checking can inadvertently be omitted. Error codes are harder to maintain because you have to keep a catalog with all error codes and then switch on the result to see what error was thrown. Error ranges can be of help here, because if the only thing we are interested in is if we are in the presence of an error or not, it is simpler to check (e.g., an HRESULT error code greater or equal to 0 is success and less than zero is failure). They can inadvertently be omitted because there is no programmatic forcing that the developer will check for error codes. On the other hand, you cannot ignore exceptions.

To summarize I prefer exceptions over error codes in almost all situations.

Solution 3 - Language Agnostic

I prefer exceptions because

  • they interupt the flow of logic
  • they benefit from class hierarchy which gives more features/functionality
  • when used properly can represent a wide range of errors (e.g. an InvalidMethodCallException is also a LogicException, as both occur when there's a bug in your code that should be detectable before runtime), and
  • they can be used to enhance the error (i.e. a FileReadException class definition can then contain code to check whether the file exists, or is locked, etc)

Solution 4 - Language Agnostic

Error codes can be ignored (and often are!) by the callers of your functions. Exceptions at least force them to deal with the error in some way. Even if their version of dealing with it is to have an empty catch handler (sigh).

Solution 5 - Language Agnostic

Exceptions over error codes, no doubt about it. You get much of the same benefits from exceptions as you do with error codes, but also much more, without the shortcomings of error codes. The only knock on exceptions is that it is slightly more overhead; but in this day and age, that overhead should be considered negligible for nearly all applications.

Here are some articles discussing, comparing and contrasting the two techniques:

There are some good links in those that can give you further reading.

Solution 6 - Language Agnostic

I would never mix the two models...it's too hard to convert from one to the other as you move from one part of the stack which is using error codes, to a higher piece that is using exceptions.

Exceptions are for "anything that stops or inhibits the method or subroutine from doing what you asked it to do" ... NOT to pass messages back about irregularities or unusual circumstances, or the state of the system, etc. Use return values or ref (or out) parameters for that.

Exceptions allow methods to be written (and utilized) with semantics that are dependent on the method's function, i.e. a method that returns an Employee object or List of Employees can be typed to do just that, and you can utilize it by calling.

Employee EmpOfMonth = GetEmployeeOfTheMonth();

With error codes, all methods return an error code, so, for those that need to return something else to be used by the calling code, you have to pass a reference variable to be populated with that data, and test the return value for the error code, and handle it, on every function or method call.

Employee EmpOfMonth; 
if (getEmployeeOfTheMonth(ref EmpOfMonth) == ERROR)
    // code to Handle the error here

If you code so that each method does one and only one simple thing, then you should throw an exception whenever the method cannot accomplish the method's desired objective. Exceptions are much richer and easier to use in this way than error codes. Your code is much cleaner - The standard flow of the "normal" code path can be devoted strictly to the case where the method IS able to accomplish what you wanted it to do... And then the code to clean up, or handle the "exceptional" circumstances when something bad happens that prevents the method from completing successfully can be siloed away from the normal code. Additionally, if you can't handle the exception where it occurred, and must pass it up the stack to a UI, (or worse, across the wire from a mid-tier component to a UI), then with the exception model, you don't need to code every intervening method in your stack to recognize and pass the exception up the stack... The exception model does this for you automagically.... With error codes, this piece of the puzzle can get onerous very rapidly.

Solution 7 - Language Agnostic

You should use both. The thing is to decide when to use each one.

There are a few scenarios where exceptions are the obvious choice:

  1. In some situations you can't do anything with the error code, and you just need to handle it in an upper level in the call stack, usually just log the error, display something to the user or close the program. In these cases, error codes would require you to bubble up the error codes manually level by level which is obviously much easier to do with exceptions. The point is that this is for unexpected and unhandleable situations.

  2. Yet about situation 1 (where something unexpected and unhandleable happens you just wan't to log it), exceptions can be helpful because you might add contextual information. For example if I get a SqlException in my lower-level data helpers, I will want to catch that error in the low-level (where I know the SQL command that caused the error) so I can capture that information and rethrow with additional information. Please note the magic word here: rethrow, and not swallow. The first rule of exception handling: do not swallow exceptions. Also, note that my inner catch doesn't need to log anything because the outer catch will have the whole stack trace and may log it.

  3. In some situations you have a sequence of commands, and if any of them fail you should cleanup/dispose resources(*), whether or not this is an unrecoverable situation (which should be thrown) or a recoverable situation (in which case you can handle locally or in the caller code but you don't need exceptions). Obviously it's much easier to put all those commands in a single try, instead of testing error codes after each method, and cleanup/dispose in the finally block. Please note that if you want the error to bubble up (which is probably what you want), you don't even need to catch it - you just use the finally for cleanup/dispose - you should only use catch/retrow if you want to add contextual information (see bullet 2).

    One example would be a sequence of SQL statements inside a transaction block. Again, this also a "unhandleable" situation, even if you decide to catch it early (treat it locally instead of bubbling up to the top) it's still a fatal situation from where the best outcome is to abort everything or at least abort a large part of the process.
    (*) This is like the on error goto that we used in old Visual Basic

  4. In constructors you can only throw exceptions.

Having said that, in all other situations where you're returning some information on which the caller CAN/SHOULD take some action, using return codes is probably a better alternative. This includes all expected "errors", because probably they should be handled by the immediate caller, and will hardly need to be bubbled up too many levels up in the stack.

Of course it's always possible to treat expected errors as exceptions, and catch then immediately one level above, and it's also possible to encompass every line of code in a try catch and take actions for each possible error. IMO, this is bad design, not only because it's much more verbose, but specially because the possible exceptions that might be thrown are not obvious without reading the source code - and exceptions could be thrown from any deep method, creating invisible gotos. They break code structure by creating multiple invisible exit points that make code hard to read and inspect. In other words, you should never use exceptions as flow-control, because that would be hard for others to understand and maintain. It can get even difficult to understand all possible code flows for testing.
Again: for correct cleanup/dispose you can use try-finally without catching anything.

The most popular criticism about return codes is that "someone could ignore the error codes, but in the same sense someone can also swallow exceptions. Bad exception handling is easy in both methods. But writing good error-code-based program is still much easier than writing an exception-based program. And if one by any reason decides to ignore all errors (the old on error resume next), you can easily do that with return codes and you can't do that without a lot of try-catchs boilerplate.

The second most popular criticism about return codes is that "it's difficult to bubble up" - but that's because people don't understand that exceptions are for non-recoverable situations, while error-codes are not.

Deciding between exceptions and error codes is a gray area. It's even possible that you need to get an error code from some reusable business method, and then you decide to wrap that into an exception (possibly adding information) and let it bubble up. But it's a design mistake to assume that ALL errors should be thrown as exceptions.

To sum it up:

  • I like to use exceptions when I have an unexpected situation, in which there's not much to do, and usually we want to abort a large block of code or even the whole operation or program. This is like the old "on error goto".

  • I like to use return codes when I have expected situations in which the caller code can/should take some action. This includes most business methods, APIs, validations, and so on.

This difference between exceptions and error codes is one of the design principles of the GO language, which uses "panic" for fatal unexpected situations, while regular expected situations are returned as errors.

Yet about GO, it also allows multiple return values , which is something that helps a lot on using return codes, since you can simultaneously return an error and something else. On C#/Java we can achieve that with out parameters, Tuples, or (my favorite) Generics, which combined with enums can provide clear error codes to the caller:

public MethodResult<CreateOrderResultCodeEnum, Order> CreateOrder(CreateOrderOptions options)
{
    ....
    return MethodResult<CreateOrderResultCodeEnum>.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area");

    ...
    return MethodResult<CreateOrderResultCodeEnum>.CreateSuccess(CreateOrderResultCodeEnum.SUCCESS, order);
}

var result = CreateOrder(options);
if (result.ResultCode == CreateOrderResultCodeEnum.OUT_OF_STOCK)
    // do something
else if (result.ResultCode == CreateOrderResultCodeEnum.SUCCESS)
    order = result.Entity; // etc...

If I add a new possible return in my method, I can even check all callers if they are covering that new value in a switch statement for example. You really can't do that with exceptions. When you use return codes, you'll usually know in advance all possible errors, and test for them. With exceptions you usually don't know what might happen. Wrapping enums inside exceptions (instead of Generics) is an alternative (as long as it's clear the type of exceptions that each method will throw), but IMO it's still bad design.

EDIT 2020-10-11:

Since C# 7.0 (March 2017) instead of Generics I prefer to use the new Tuples syntax which allows multiple return values (so we can use GO-like syntax where methods return a result OR an error).


public enum CreateUserResultCodeEnum
{
    [Description("Username not available")]
    NOT_AVAILABLE,
}

public (User user, CreateUserResultCodeEnum? error) CreateUser(string userName)
    // (try to create user, check if not available...)
    if (notAvailable)
        return (null, CreateUserResultCodeEnum.NOT_AVAILABLE);
    return (user, null);
}

// How to call and deconstruct tuple:
(var user, var error) = CreateUser("john.doe");
if (user != null) ...
if (error == CreateUserResultCodeEnum.NOT_AVAILABLE) ...

// Or returning a single object (named tuple):
var result = CreateUser("john.doe");
if (result.user != null) ...
if (result.error == CreateUserResultCodeEnum.NOT_AVAILABLE) ...

EDIT 2021-01-09:

A few days ago I wrote this blog post about how we can (in some cases!) use multiple returns instead of exceptions (like golang convention explained above, not supposed to replace all your exceptions but supposed to give you arsenal to decide between when to use exceptions and when to use return codes). By the end of the post I'm mixing two models - basically I'm using the ValueTuple syntax (which is very concise and elegant) but yet using Generics as the underlying structure. Basically I use implicit conversion operator and type deconstructors to convert back and forth between ValueTuple and CommandResult<TEntity, TError>.

Solution 8 - Language Agnostic

In the past I joined the errorcode camp (did too much C programming). But now I have seen the light.

Yes exceptions are a bit of a burden on the system. But they simplify the code, reducing the number of errors (and WTF's).

So use exception but use them wise. And they will be your friend.

As a side note. I have learned to document which exception can be thrown by which method. Unfortunately this is not required by most languages. But it increases the chance of handling the right exceptions at the right level.

Solution 9 - Language Agnostic

There may be a few situations where using exceptions in a clean, clear, correct way is cumbersome, but the vast majority of the time exceptions are the obvious choice. The biggest benefit exception handling has over error codes is that it changes the flow of execution, which is important for two reasons.

When an exception occurs, the application is no longer following it's 'normal' execution path. The first reason why this is so important is that unless the author of the code goes well and truly out of their way to be bad, the program will halt and not continue doing unpredictable things. If an error code doesn't get checked and appropriate actions aren't taken in response to a bad error code, the program will keep on doing what it's doing and who knows what the result of that action will be. There are lots of situations where having the program do 'whatever' could wind up being very expensive. Consider a program that retrieves performance information for various financial instruments a company sells, and delivers that information to brokers/wholesalers. If something goes wrong and the program keeps going, it could ship erroneous performance data to the brokers and wholesalers. I don't know about anybody else, but I don't want to be the one sitting in a VPs office explaining why my code caused the company to get 7-figures worth of regulatory fines. Delivering an error message to customers is generally preferable to delivering wrong data that could look to be 'real', and the latter situation is much easier to run into with a much less aggressive approach like error codes.

The second reason why I like exceptions and their breaking of the normal execution is that it makes it much, much easier to keep the 'normal things are happening' logic separate from the 'something went wrong logic'. To me, this:

try {
    // Normal things are happening logic
catch (// A problem) {
    // Something went wrong logic
}

...is preferable to this:

// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}

There are other little things about exceptions that are nice, as well. Having a bunch of conditional logic to keep track of whether any of the methods being called in a function had an error code returned, and return that error code higher up is a lot of boiler plate. In fact, it's a lot of boiler plate that can go wrong. I have a lot more faith in the exception system of most languages than I do a rats nest of if-else-if-else statements that 'Fresh-out-of-college' Fred wrote, and I have a lot better things to do with my time than code reviewing said rat's nest.

Solution 10 - Language Agnostic

My reasoning would be if you are writing a low-level driver that really needs performance, then use error codes. But if you're using that code in a higher-level application and it can handle a bit of overhead, then wrap that code with an interface which checks those error codes and raises exceptions.

In all other cases, exceptions are probably the way to go.

Solution 11 - Language Agnostic

My approach is that we can use both, i.e. Exceptions and Errors codes at the same time.

I'm used to define several types of Exceptions (ex: DataValidationException or ProcessInterruptExcepion) and inside each exception define a more detailed description of each problem.

A Simple Example in Java:

public class DataValidationException extends Exception {

    
    private DataValidation error;
    
    /**
     * 
     */
    DataValidationException(DataValidation dataValidation) {
        super();
        this.error = dataValidation;
    }


}

enum DataValidation{
    
    TOO_SMALL(1,"The input is too small"),
    
    TOO_LARGE(2,"The input is too large");
    
  
    private DataValidation(int code, String input) {
        this.input = input;
        this.code = code;
    }

    private String input;
    
    private int code;

}

In this way i use Exceptions to define category errors, and error codes to define more detailed info about the problem.

Solution 12 - Language Agnostic

I may be sitting on the fence here, but...

  1. It depends on the language.
  2. Whichever model you choose, be consistent about how you use it.

In Python, use of exceptions is standard practice, and I'm quite happy to define my own exceptions. In C you don't have exceptions at all.

In C++ (in the STL at least), exceptions are typically only thrown for truly exceptional errors (I virtually never see them myself). I see no reason to do anything different in my own code. Yes it's easy to ignore return values, but C++ doesn't force you to catch exceptions either. I think you just have to get into the habit of doing it.

The code base I work on is mostly C++ and we use error codes almost everywhere, but there's one module that raises exceptions for any error, including very unexceptional ones, and all the code that uses that module is pretty horrible. But that might just be because we've mixed exceptions and error codes. The code that consistently uses error codes is much easier to work with. If our code consistently used exceptions, maybe it wouldn't be as bad. Mixing the two doesn't seem to work so well.

Solution 13 - Language Agnostic

Since I work with C++, and have RAII to make them safe to use, I use exceptions almost exclusively. It pulls error handling out of the normal program flow and makes the intent more clear.

I do leave exceptions for exceptional circumstances though. If I'm expecting that a certain error is going to happen a lot I'll check that the operation will succeed before performing it, or call a version of the function that uses error codes instead (Like TryParse())

Solution 14 - Language Agnostic

Method signatures should communicate to you what the method does. Something like long errorCode = getErrorCode(); might be fine, but long errorCode = fetchRecord(); is confusing.

Solution 15 - Language Agnostic

Exceptions are for exceptional circumstances - ie, when they are not part of the normal flow of the code.

It's quite legitimate to mix Exceptions and error codes, where error codes represent the status of something, rather than an error in the running of the code per se (e.g. checking the return code from a child process).

But when an exceptional circumstance occurs I believe Exceptions are the most expressive model.

There are cases where you might prefer, or have, to use error codes in place of Exceptions, and these have been adequately covered already (other than other obvious constrains such as compiler support).

But going in the other direction, using Exceptions allows you to build even higher level abstractions to your error handling, that can make your code even more expressive and natural. I would highly recommend reading this excellent, yet underrated, article by C++ expert Andrei Alexandrescu on the subject of what he calls, "Enforcements": http://www.ddj.com/cpp/184403864. Although it's a C++ article the principles are generally applicable, and I have translated the enforcements concept to C# quite successfully.

Solution 16 - Language Agnostic

First, I agree with Tom's answer that for high-level stuff use exceptions, and for low-level stuff use error codes, as long as it is not Service Oriented Architecture (SOA).

In SOA, where methods may be called across different machines, exceptions may not be passed over the wire, instead, we use success/failure responses with a structure like below (C#):

public class ServiceResponse
{
    public bool IsSuccess => string.IsNullOrEmpty(this.ErrorMessage);

    public string ErrorMessage { get; set; }
}

public class ServiceResponse<TResult> : ServiceResponse
{
    public TResult Result { get; set; }
}

And use like this:

public async Task<ServiceResponse<string>> GetUserName(Guid userId)
{
    var response = await this.GetUser(userId);
    if (!response.IsSuccess) return new ServiceResponse<string>
    {
        ErrorMessage = $"Failed to get user."
    };
    return new ServiceResponse<string>
    {
        Result = user.Name
    };
}

When these are used consistently in your service responses it creates a very nice pattern of handling success/failures in the application. This allows easier error handling in async calls within services as well as across services.

Solution 17 - Language Agnostic

I would prefer Exceptions for all error cases, except when a failure is an expectable bug-free result of a function that returns a primitive datatype. E.g. finding the index of a substring within a larger string would usually return -1 if not found, instead of raising a NotFoundException.

Returning invalid pointers that might be dereferenced (e.g. causing NullPointerException in Java) is not acceptable.

Using multiple different numerical error codes (-1, -2) as return values for the same function is usually bad style, as clients might do a "== -1" check instead of "< 0".

One thing to keep in mind here is the evolution of APIs over time. A good API allows to change and extend failure behavior in several ways without breaking clients. E.g. if a client error handle checked for 4 error cases, and you add a fifth error value to your function, the client handler may not test this and break. If you raise Exceptions, this will usually make it easier for clients to migrate to a newer version of a library.

Another thing to consider is when working in a team, where to draw a clear line for alldevelopers to make such a decision. E.g. "Exceptions for high-level stuff, error codes for low-level stuff" is very subjective.

In any case, where more than one trivial type of error is possible, the source code should never use the numeric literal to return an error code or to handle it (return -7, if x == -7 ...), but always a named constant (return NO_SUCH_FOO, if x == NO_SUCH_FOO) .

Solution 18 - Language Agnostic

If you work under big project, you can't use only exceptions or only error codes. In different cases you should use different approaches.

For example, you decide to use exceptions only. But once you decide to use async event processing. It is bad idea to use exceptions for error handling in this situations. But use error codes everywhere in application is tedious.

So my opinion that it is normal to use both exceptions and error codes simultaneous.

Solution 19 - Language Agnostic

For most applications, exceptions are better. The exception is when the software has to communicate with other devices. The domain I work in is industrial controls. Here errors codes are preferred and expected. So my answer is that it does depend on the situation.

Solution 20 - Language Agnostic

I think it also depends on whether you really need information like stack trace from the result. If yes, you definitely go for Exception which provide object full with lots of information about problem. However, if you are just interested in result and don't care why that result then go for error code.

e.g. When you are processing file and face IOException, client might interested in knowing from where this was triggered, in opening file or parsing file etc. So better you return IOException or its specific subclass. However, scenario like you have login method and you want to know it was successful or not, there either you just return boolean or to show correct message, return error code. Here Client is not interested in knowing which part of logic caused that error code. He just know if its Credential invalid or account lock etc.

Another usecase I can think of is when data travels on network. Your remote method can return just error code instead of Exception to minimize data transfer.

Solution 21 - Language Agnostic

My general rule is:

  • Only one error could appear in a function: use error code (as parameter of the function)
  • More than one specific error could appear: throw exception

Solution 22 - Language Agnostic

Error codes also don't work when your method returns anything other than a numeric value...

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJorge FerreiraView Question on Stackoverflow
Solution 1 - Language AgnosticTom DunhamView Answer on Stackoverflow
Solution 2 - Language AgnosticJorge FerreiraView Answer on Stackoverflow
Solution 3 - Language AgnosticJamShadyView Answer on Stackoverflow
Solution 4 - Language AgnosticMaxamView Answer on Stackoverflow
Solution 5 - Language AgnosticPistosView Answer on Stackoverflow
Solution 6 - Language AgnosticCharles BretanaView Answer on Stackoverflow
Solution 7 - Language AgnosticdrizinView Answer on Stackoverflow
Solution 8 - Language AgnosticToon KrijtheView Answer on Stackoverflow
Solution 9 - Language AgnosticDogsView Answer on Stackoverflow
Solution 10 - Language AgnosticClaudiuView Answer on Stackoverflow
Solution 11 - Language AgnosticsakanaView Answer on Stackoverflow
Solution 12 - Language AgnosticjrbView Answer on Stackoverflow
Solution 13 - Language AgnosticEclipseView Answer on Stackoverflow
Solution 14 - Language AgnosticPaul CroarkinView Answer on Stackoverflow
Solution 15 - Language AgnosticphilsquaredView Answer on Stackoverflow
Solution 16 - Language AgnosticoradView Answer on Stackoverflow
Solution 17 - Language AgnostictkruseView Answer on Stackoverflow
Solution 18 - Language AgnosticgomonsView Answer on Stackoverflow
Solution 19 - Language AgnosticJim CView Answer on Stackoverflow
Solution 20 - Language AgnosticashahView Answer on Stackoverflow
Solution 21 - Language AgnosticDEADBEEFView Answer on Stackoverflow
Solution 22 - Language AgnosticOmar KoohejiView Answer on Stackoverflow