Should a failed function return a value or throw an exception?

Tags:

You have created a nice, well written function, but you realize you forgot something: The failure case.

What should a function do when it fails? There are two schools for this – the “throw an exception” school and the “return an error value” school.

But which of these is the correct approach?

Some background

Back in ye olden times there were no exceptions. Languages like C are still being used today. In such languages the choice is quite easy to make.

However, what about languages that do support exceptions? Should you strive to use the language’s features to their fullest, and always throw an exception? Are error returns just relics?

If you listen to people talk about this, it often turns into a debate about what they like. If you grew up with languages that had no exceptions, chances are you still prefer return codes. Similarly, if you used a lot of Java or other exceptional language, you might end up disliking return codes.

However – both of them deserve merit and have their uses.

Basics

As you may have heard, exceptions are for exceptional conditions.

But what is an exceptional condition? What isn’t?

Let’s take an example: Your application attempts to load a resource file – for example the configuration settings for your database – and it’s missing.

Is this an exceptional condition? Maybe. It depends on one thing: Is it normal for it to be missing, or should the file always be there?

function loadConfiguration() {
  var file = open('configuration.ini')
 
  if(exists(file)) {
    read(file)
  }
  else {
    throw "Configuration file is missing"
  }
}

So this pseudo-code could be a simplified configuration loading function. In this case the configuration file has to exist – let’s say it’s some internal file.

But what if the file is something that contains settings the user has selected? In that case it might be perfectly fine for the file to not exist. The user may not have added any custom settings and as such, it’s normal for the file to be missing. In this case our function could look like this:

function tryLoadConfiguration() {
  var file = open('configuration.ini')
 
  if(exists(file)) {
    read(file)
    return true
  }
  else {
    return false
  }
}

Note that I also changed the name of the function a bit. It may be a good idea to try indicating that the function will be returning a value – It tries to do it, so the name is more expressive of the permissive nature and that it’ll return whether it succeeded or not.

Other examples

Other good examples are various string related functions. In many languages, there exists a function to determine whether a string contains another string, and this function usually return -1 on failure.

It’s normal for this function to not succeed, after all, we are using it to test whether or not the condition is met.

There are also more “pure” arguments for either side. Exceptions are essentially objects, so they can convey more information than just the fact that things went wrong. On the other hand, an exception can be thrown deep in the code but appear on the surface unless caught early enough. This could make tracking the origin and cause of the exception more difficult.

Ned Batcheler has some good reasons for exceptions compared to error returns on his blog in addition to these. However, is this all?

Deeper considerations

It’s not all just talk about exceptional conditions or technological preferences.

An often forgotten fact is application stability. If you are writing critical code that should be as stable as possible, what should you use? Return values.

An uncaught exception can bring down a whole application. An unhandled return code might not really cause anything at all. Sometimes it’s important to consider this factor too.

One interesting alternative is using a type to indicate the result. For example, in Haskell, you can have a value of type Maybe. This indicates that it can have a value, or it might not have one at all. The way this is useful is that this can be used to indicate when special handling is required – for example, in order to get the value out of a Maybe, you need to do it differently than accessing non-Maybe values.

Conclusion

We can say that exceptions can be convenient. We can also say you can easily use exceptions too much, and that how to decide whether to use them or return codes is difficult. It comes with experience, but I hope this post sparked some ideas for you. Damien Katz’s blog also has a (long, long) post on exceptions, error codes and reliability which is kind of related.