Why Return Codes Make Me Mad


In which I go on and on complaining about return codes, letting off some steam.

So, return codes. They make me mad. When given the choice, I always prefer exceptions.

Let’s say you have a function getValue(), which returns, well, the value of something. However, if something went wrong during setup, the value might never have been set, and we need to detect that. What we would like to write is

type value = getValue(); //For some type bool / int / WhateverClass

We do however need to detect error conditions. Either we use exceptions, or we use return codes. We can do either

try {
    type value = getValue();
} catch (NotInitializedException&) {
    //handle
}

or

type value;
bool ok = getValue(value)
if (!ok) {
    //handle
}

Either way you need to look out for errors, and maybe one isn’t so much worse than the other. What return codes do to the flow of you code however, is much worse. Here are some of the things that annoy me the most:

The size of the code explodes
An innocent, readable line like

if (getValue() == whatever)

turns into this mess:

type value;
getValue(value)
if(value == whatever)

Which one is easiest to read?

And of course, you can’t do this anymore:

finalResult = doStuffWith(getValue());

Variables have intermediate, invalid values
In the previous example, value needs to be constructed before getValue() is called. There’s a whole list of problems with this:

  • If it is a fundamental type (int, float, bool etc.) you have to manually initialize it, or you will have a dangerous uninitialized variable.
  • Some types don’t even have a default constructor
  • Some types are expensive to construct
  • Some constructors have side effects
  • value will have a meaningless value for a (short) part of the life of the program

You cannot use const variables.
I always make variables const whenever I can. This helps with program correctness, because I will never accidentally modify them, or pass them to a function that modifies them. But now you can’t do

const type value = getValue();

you have to do

type value; //Can't use const
getValue(value) //because it's modified here

Error checking code will infect your code and make it unreadable

Instead of handling errors in one place like this:

try {
    const type value = getValue()
    const othertype othervalue = getRelatedValue(value);
    doStuff(othervalue);
} catch (NotInitializedException&) {
    //handle;
}

You have to check for errors all the time:

type value;
bool ok = getValue(value);
if (ok) {
    othertype othervalue;
    bool otherok = getRelatedValue(othervalue, value);
    if (otherok) {
        doStuff(othervalue);
    } else {
        //handle
    }
} else {
    //handle
}

Look at this mess! Notice how the error handling code gets intermixed with the happy code. In the version of the code using exceptions, this is not necessary, as the rest of the try block is automatically skipped when an exception is thrown.

Constructors can’t even return a return code
The return value of a constructor always is the constructed object, so here you don’t even have a choice. If you don’t want to use exceptions, you need to remember that the object is now in an invalid state. So either you hope your users remember to do

SillyType s;
if (!s.gotProperlyConstructed() {
    //handle
}

or you need to make the newly constructed object remember that it is invalid, and return that as an error code indicating that every time someone calls a method on it from now on.

The code does not say what it really means
An argument to a function is an argument to a function. The return value of a function is the return value of a function. When reading a piece of code, this is the natural way to reason about it. When arguments are suddenly used for return values, the natural flow of the code is broken.

That’s not all
In addition, there’s a host of other problems like:

  • It’s easy to forget to check a return code, leaving your program silently in an invalid state. However, if you forget to catch an exception, you program blows up and at least you know something is wrong.
  • Operators can’t use return codes.
  • Exceptions tend to have useful information attached to them, describing the error. Return codes are usually enums, integers or booleans. (This can be fixed though, by returning an error object instead of an error code.)

Readable code
In all of the examples above, return codes lead to code that is less readable. And for me, readable code is extremely important. When your code is unreadable:

  • It is easier for bugs to hide
  • It is harder to make changes
  • It takes longer to get to know the code for new programmers (which might be yourself, half a year later)
  • It is usually harder to write tests
  • It is scary to refactor because you don’t understand the code fully, so you end up patching on another few lines and another few lines instead

In short, unreadable code leads to more bugs, longer development time for new features, more time spent in the debugger, and more time spent on maintenance. This is why I hate unreadable code. This is why return codes make me mad.

What can you do?
For new projects, go with exceptions. For legacy code you usually need to stay with the existing style. If all the code around you uses return codes, you probably should too. I would however argue that there still can be a place for exceptions: If you are extending the code base with an isolated module, or doing a major refactoring of one, and the interface to the rest of the code is small, use exceptions internally. Then catch the exceptions on the boundary and translate to return codes. After a while, you will gradually move towards exceptions and more readable code for the entire code base.

If you enjoyed this post, you can subscribe to my blog, or follow me on Twitter.

13 Responses to “Why Return Codes Make Me Mad”

  1. Robert Says:

    Great article! Btw, Common Lisp also takes exceptions to the next level, allowing restarts. Imagine writing code like this:

    if (something_is_wrong) {
    sometype restart_value = throw some_exception(some, args);
    do_something_sensible_instead();
    }

    and in the other end:

    try {
    call_function_with_restart();
    }
    catch (std::restartable_exception& e) {
    if (e.something()) {
    e.restart(some_code);
    }

    }

    Here, e.restart() will never return, and jump back to the point of the throw, returning the argument
    passed from throw.

    I also found an error:
    SillyType s();
    Creates an object or declares a function?

    • Anders Schau Knatten Says:

      That is cool! I incidentally read about this just last weekend in Stuart Halloway’s Rifle-Oriented Programming with Clojure, I hadn’t heard about it before. Unfortunately, I don’t expect to see this is the next C++ standard! :)

      Thanks for the heads up on the SillyType, it was indeed silly. I have fixed it now.

      • JonKalb Says:

        You are correct to not expect to see this in the next standard, but I don ‘t know how unfortunate that it.

        In “The Design and Evolution of C++,” Bjarne Stroustrup’s book on how C++ got to be C++, he discusses how exceptions were defined for C++. There was indeed a debate on “Resumption vs. Termination.” (See Section 16.6 of the book.)

        After careful consideration and discussion of experiences with other languages, resumption was rejected in favor the form we have in C++ today. I’ll let you read the book for the technical details, but I very much doubt that it will be considered again in future standards.

        • Anders Schau Knatten Says:

          I didn’t know there had been a debate about it, I mentioned it more tongue-in-cheek. It certainly feels more like a Lisp-y feature than a C++-y one. Thanks for reminding me of that book by the way, I still haven’t gotten around to reading it.

      • Anders Schau Knatten Says:

        If anyone is interested in knowing more about restarts, here is a very easy-to-read explanation, using Python for the examples instead of Lisp: http://lubutu.com/soso/condition-handling-for-non-lispers

  2. JonKalb Says:

    Nice post. Some of these ideas are already in my talk on exception-safe code (at http://exceptionsafecode.com), but some are not covered and I’ll have to gives some thought about where they fit for the next time I present it.

  3. Richard Powell Says:

    Andrei Alexandrescu presented an idea at C++ and Beyond 2012 called Expected. The idea is that a function returns an Expected, which behaves like a std::future. When you call Expected::get(), it returns the value, or throws the exception that would have been thrown – the exception that prevented the value’s creation.

    You would use it this way:
    int Foo();

    int result = Foo();
    if (result == 10) { … }

    Becomes to

    Expected Foo();

    Expected result = Foo();
    if (result.get() == 10) { … }

    It provides a couple of things that you can’t do with normal exceptions, notably you can have several exceptions “active”, ie, you can do the following

    Expected resultA = Foo(); // potentially an exception
    Expected resultB = Foo(); // potentially an exception
    Expected resultC = Foo(); // potentially an exception

    • Anders Schau Knatten Says:

      That’s a very interesting use of stored exceptions! It does make a lot of sense to do this for futures of course, but I am not sure I would use them in this way.

      It will be interesting to see if this idea catches on, and to see some scenarios in which this will be useful. Generally I want my exceptions thrown as soon as possible.

  4. Joe Says:

    lame…. return codes are fine

    • Jon Kalb Says:

      Joe,

      I’m convinced.

      Anders’ insightful, well-reasoned and thoughtful presentation had me believing that exceptions were superior to return codes for all the good software engineering reason he presents here.

      But you’ve totally turned me around. I didn’t realize that Anders’ (who regularly posts insightful thoughts on improving the state of the art of software engineering) had created a post that was “lame.” Or that, notwithstanding the score of well-reasoned and presented problems presented here, return codes are “fine.”

      Your well-researched and articulately presented, point-by-point response was powerful and convincing, not to mention witty and concise.

      Thanks for contributing to the discourse. Your quest for knowledge, your ambition to better your professional practice, your zest for constant improvement, your uncompromising opposition to mediocrity in code, and your generous nature in sharing the wisdom of your experience is a both a inspiration and a gift to the large software engineering community.

      Jon

  5. Jonathan Says:

    We use return codes over Exceptions currently, and for a reason that this article does not cover:
    Exceptions are many times slower than return codes.

    For most programmers they wont even notice the difference, but we deal with realtime processes (~.01ms-10ms response times) in which functions are iterated hundreds, thousands or even millions of times. The overhead with using exceptions has proven to be unacceptably slow for our use.

    • JonKalb Says:

      The model with exceptions is that if an exception is thrown, then you are bailing out and will not complete the operation (you don’t have a loop of thrown exceptions). So the concern is correctness, not performance. This is the correct model for most users. But there are circumstances, and it sounds like your’s is among them, Jonathan, where even in the failure case, the response window is very tight. (In other words, it is okay to fail, but it is not okay to be slow returning.)

      In such circumstances (which I think are rarer than most people believe), exceptions may not be the optimal error handling approach.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: