Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which, and why, do you prefer Exceptions or Return codes?

My question is what do most developers prefer for error handling, Exceptions or Error Return Codes. Please be language(or language family) specific and why you prefer one over the other.

I'm asking this out of curiosity. Personally I prefer Error Return Codes since they are less explosive and don't force user code to pay the exception performance penalty if they don't want to.

update: thanks for all the answers! I must say that although I dislike the unpredictability of code flow with exceptions. The answer about return code (and their elder brother handles) do add lots of Noise to the code.

like image 281
Robert Gould Avatar asked Sep 19 '08 04:09

Robert Gould


People also ask

Why are exceptions better than return codes?

An application that uses exceptions is more robust than an application that uses return codes. An application that uses exceptions can also give the cleanest code, since return codes don't have to be checked after every call.

Why are exceptions better than assertions?

Exceptions versus assertions Use assert statements to test for conditions during development that should never be true if all your code is correct. There's no point in handling such an error by using an exception, because the error indicates that something in the code has to be fixed.

What are the benefits of using exceptions?

Exceptions provide the means to separate the details of what to do when something out of the ordinary happens from the main logic of a program. In traditional programming, error detection, reporting, and handling often lead to confusing spaghetti code.

What is the purpose of exceptions in coding?

An exception, in programming, is an unplanned event, such as invalid input or a loss of connectivity, that occurs while a program is executing and disrupts the flow of its instructions. Exception is a short way of saying exceptional event. In Java, exceptions exist as a class, java.


2 Answers

For some languages (i.e. C++) Resources leak should not be a reason

C++ is based on RAII.

If you have code that could fail, return or throw (that is, most normal code), then you should have your pointer wrapped inside a smart pointer (assuming you have a very good reason to not have your object created on stack).

Return codes are more verbose

They are verbose, and tend to develop into something like:

if(doSomething()) {    if(doSomethingElse())    {       if(doSomethingElseAgain())       {           // etc.       }       else       {          // react to failure of doSomethingElseAgain       }    }    else    {       // react to failure of doSomethingElse    } } else {    // react to failure of doSomething } 

In the end, you code is a collection of idented instructions (I saw this kind of code in production code).

This code could well be translated into:

try {    doSomething() ;    doSomethingElse() ;    doSomethingElseAgain() ; } catch(const SomethingException & e) {    // react to failure of doSomething } catch(const SomethingElseException & e) {    // react to failure of doSomethingElse } catch(const SomethingElseAgainException & e) {    // react to failure of doSomethingElseAgain } 

Which cleanly separate code and error processing, which can be a good thing.

Return codes are more brittle

If not some obscure warning from one compiler (see "phjr" 's comment), they can easily be ignored.

With the above examples, assume than someone forgets to handle its possible error (this happens...). The error is ignored when "returned", and will possibly explode later (i.e. a NULL pointer). The same problem won't happen with exception.

The error won't be ignored. Sometimes, you want it to not explode, though... So you must chose carefully.

Return Codes must sometimes be translated

Let's say we have the following functions:

  • doSomething, which can return an int called NOT_FOUND_ERROR
  • doSomethingElse, which can return a bool "false" (for failed)
  • doSomethingElseAgain, which can return an Error object (with both the __LINE__, __FILE__ and half the stack variables.
  • doTryToDoSomethingWithAllThisMess which, well... Use the above functions, and return an error code of type...

What is the type of the return of doTryToDoSomethingWithAllThisMess if one of its called functions fail ?

Return Codes are not a universal solution

Operators cannot return an error code. C++ constructors can't, too.

Return Codes means you can't chain expressions

The corollary of the above point. What if I want to write:

CMyType o = add(a, multiply(b, c)) ; 

I can't, because the return value is already used (and sometimes, it can't be changed). So the return value becomes the first parameter, sent as a reference... Or not.

Exception are typed

You can send different classes for each kind of exception. Ressources exceptions (i.e. out of memory) should be light, but anything else could be as heavy as necessary (I like the Java Exception giving me the whole stack).

Each catch can then be specialized.

Don't ever use catch(...) without re-throwing

Usually, you should not hide an error. If you do not re-throw, at the very least, log the error in a file, open a messagebox, whatever...

Exception are... NUKE

The problem with exception is that overusing them will produce code full of try/catches. But the problem is elsewhere: Who try/catch his/her code using STL container? Still, those containers can send an exception.

Of course, in C++, don't ever let an exception exit a destructor.

Exception are... synchronous

Be sure to catch them before they bring out your thread on its knees, or propagate inside your Windows message loop.

The solution could be mixing them?

So I guess the solution is to throw when something should not happen. And when something can happen, then use a return code or a parameter to enable to user to react to it.

So, the only question is "what is something that should not happen?"

It depends on the contract of your function. If the function accepts a pointer, but specifies the pointer must be non-NULL, then it is ok to throw an exception when the user sends a NULL pointer (the question being, in C++, when didn't the function author use references instead of pointers, but...)

Another solution would be to show the error

Sometimes, your problem is that you don't want errors. Using exceptions or error return codes are cool, but... You want to know about it.

In my job, we use a kind of "Assert". It will, depending on the values of a configuration file, no matter the debug/release compile options:

  • log the error
  • open a messagebox with a "Hey, you have a problem"
  • open a messagebox with a "Hey, you have a problem, do you want to debug"

In both development and testing, this enable the user to pinpoint the problem exactly when it is detected, and not after (when some code cares about the return value, or inside a catch).

It is easy to add to legacy code. For example:

void doSomething(CMyObject * p, int iRandomData) {    // etc. } 

leads a kind of code similar to:

void doSomething(CMyObject * p, int iRandomData) {    if(iRandomData < 32)    {       MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;       return ;    }     if(p == NULL)    {       MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;       throw std::some_exception() ;    }     if(! p.is Ok())    {       MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;    }     // etc. } 

(I have similar macros that are active only on debug).

Note that on production, the configuration file does not exist, so the client never sees the result of this macro... But it is easy to activate it when needed.

Conclusion

When you code using return codes, you're preparing yourself for failure, and hope your fortress of tests is secure enough.

When you code using exception, you know that your code can fail, and usually put counterfire catch at chosen strategic position in your code. But usually, your code is more about "what it must do" then "what I fear will happen".

But when you code at all, you must use the best tool at your disposal, and sometimes, it is "Never hide an error, and show it as soon as possible". The macro I spoke above follow this philosophy.

like image 191
paercebal Avatar answered Oct 14 '22 11:10

paercebal


I use both actually.

I use return codes if it's a known, possible error. If it's a scenario that I know can, and will happen, then there's a code that gets sent back.

Exceptions are used solely for things that I'm NOT expecting.

like image 25
Stephen Wrighton Avatar answered Oct 14 '22 09:10

Stephen Wrighton