ChatGPT解决这个技术问题 Extra ChatGPT

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.

A chicken or the egg problem from the world of software... eternally debatable. :)
Sorry about that, but hopefully having a variety of opinions will help people (myself included) choose appropriately.
Generally, the use of exceptions for control flow is an anti-pattern softwareengineering.stackexchange.com/questions/189222/…

A
Ayxan Haqverdili

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.


Yeah, BUT regarding your first example, that could be easily written as: if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
Why not... But then, I still find doSomething(); doSomethingElse(); ... better because if I need to add if/while/etc. statements for normal execution purposes, I don't want them mixed with if/while/etc. statements added for exceptional purposes... And as the real rule about using exceptions is to throw, not to catch, the try/catch statements usually are not invasive.
Your first point shows what the problem with exceptions is. Your control flow gets weird and is seperated from the actual problem. It is replacing some levels of identation by a cascade of catches. I would use both, return codes (or return objects with heavy information) for possible errors and exceptions for things which are not expected.
@Peter Weber : It is not weird. It is separated from the actual problem because it is not part of the normal execution flow. It is an exceptional execution. And then, again, the point about exception is, in case of exceptional error, to throw often, and catch rarely, if ever. So the catch blocks rarely even appears in the code.
The example in this kind of debate are very simplistic. Usually, "doSomething()" or "doSomethingElse()" actually perform something, like changing a state of some object. The exception code does not guarantee returning the object to the previous state and even less when the catch is very far from the throw... As an example, imagine doSomething is called twice, and increment a counter before throwing. How do you know when catching the exception that you should decrement once or twice? In general, writing exception safe code for anything that's not a toy example is very hard (impossible ?).
S
Stephen Wrighton

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.


+1 for very simple way to go. Atleast Its short enough so that I can read it very quickly. :-)
"Exceptions are used solely for things that I'm NOT expecting." If you are not expecting them, then why do or how can you use them?
Just because I'm not expecting it to happen, does not mean that I can't see how it could. I expect my SQL Server to be turned on and responding. But I still code my expectations so that I can fail gracefully if an unexpected downtime is occurring.
Wouldn't an unresponsive SQL Server be easily categorized under "known, possible error"?
@StephenWrighton if you can see how something can happen, then how can you not expect it? it works both ways. you seem to be making a different distinction
h
hurst

According to Chapter 7 titled "Exceptions" in Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, numerous rationales are given for why using exceptions over return values is necessary for OO frameworks such as C#.

Perhaps this is the most compelling reason (page 179):

"Exceptions integrate well with object-oriented languages. Object-oriented languages tend to impose constraints on member signatures that are not imposed by functions in non-OO languages. For example, in the case of constructors, operator overloads, and properties, the developer has no choice in the return value. For this reason, it is not possible to standardize on return-value-based error reporting for object-oriented frameworks. An error reporting method, such as exceptions, which is out of band of the method signature is the only option."


Highly recommend giving this chapter a read. The chapter gives a very systematic guide to error/exception handling with lots of dos and don'ts that I cannot find online.
J
Jason Etheridge

My preference (in C++ and Python) is to use exceptions. The language-provided facilities make it a well-defined process to both raise, catch and (if necessary) re-throw exceptions, making the model easy to see and use. Conceptually, it's cleaner than return codes, in that specific exceptions can be defined by their names, and have additional information accompanying them. With a return code, you're limited to just the error value (unless you want to define an ReturnStatus object or something).

Unless the code you're writing is time-critical, the overhead associated with unwinding the stack is not significant enough to worry about.


Remember that using exceptions makes program analysis harder.
j
johnc

Exceptions should only be returned where something happens that you were not expecting.

The other point of exceptions, historically, is that return codes are inherently proprietary, sometimes a 0 could be returned from a C function to indicate success, sometimes -1, or either of them for a fail with 1 for a success. Even when they are enumerated, enumerations can be ambiguous.

Exceptions can also provide a lot more information, and specifically spell out well 'Something Went Wrong, here's what, a stack trace and some supporting information for the context'

That being said, a well enumerated return code can be useful for a known set of outcomes, a simple 'heres n outcomes of the function, and it just ran this way'


b
b_levitt

Always use exceptions by default, but consider providing an additional tester-doer option (TryX)! For me the answer is really clear. When the context dictates a Try or Tester-Doer pattern (ie cpu intensive or public api), I will ADDITIONALLY provide those methods to the exception throwing version. I think blanket rules of avoiding exceptions are misguided, unsupported, and likely cause far more expense in terms of bugs, than any performance issues they claim to prevent.

No, Microsoft DOES NOT say to not use exceptions (common misinterpretation).

It says if you're designing an API provide ways to help a user of that API to avoid THROWING exceptions if they need too (Try and Tester-Doer patterns)

❌ DO NOT use exceptions for the normal flow of control, if possible. Except for system failures and operations with potential race conditions, framework designers should design APIs so users can write code that does not throw exceptions. For example, you can provide a way to check preconditions before calling a member so users can write code that does not throw exceptions.

What is inferred here is that the non-tester-doer/non-try implementation SHOULD throw an exception upon failure and then the user CAN change that to one of your tester-doer or try methods for performance. Pit of success is maintained for safety and the user OPTS INTO the more dangerous but more performant method.

Microsoft DOES say to NOT use return codes TWICE, here:

❌ DO NOT return error codes. Exceptions are the primary means of reporting errors in frameworks. ✔️ DO report execution failures by throwing exceptions.

and here:

❌ DO NOT use error codes because of concerns that exceptions might affect performance negatively. To improve performance, it is possible to use either the Tester-Doer Pattern or the Try-Parse Pattern, described in the next two sections.

If you're not using exceptions you're probably breaking this other rule of returning return codes or booleans from a non-tester/non-try implementation. Again, TryParse does not replace Parse. It is provided in addition to Parse

MAIN REASON: Return codes fail the "Pit of Success" test for me almost every time.

It is far too easy to forget to check a return code and then have a red-herring error later on. var success = Save()? How much performance is worth someone forgetting an if check here? var success = TrySave()? Better, but are we going to abuse everything with the TryX pattern? Did you still provide a Save method?

var success = Save()? How much performance is worth someone forgetting an if check here?

var success = TrySave()? Better, but are we going to abuse everything with the TryX pattern? Did you still provide a Save method?

Return codes don't have any of the great debugging information on them like call stack, inner exceptions.

Return codes do not propagate which, along with the point above, tends to drive excessive and interwoven diagnostic logging instead of logging in one centralized place (application and thread level exception handlers).

Return codes tend to drive messy code in the form of nested 'if' blocks

Developer time spent debugging an unknown issue that would otherwise have been an obvious exception (pit of success) IS expensive.

If the team behind C# didn't intend for exceptions to govern control flow, execeptions wouldn't be typed, there would be no "when" filter on catch statements, and there would be no need for the parameter-less 'throw' statement.

Regarding Performance:

Exceptions may be computationally expensive RELATIVE to not throwing at all, but they're called EXCEPTIONS for a reason. Speed comparisons always manage to assume a 100% exception rate which should never be the case. Even if an exception is 100x slower, how much does that really matter if it only happens 1% of the time?

Context is everything. For example, A Tester-Doer or Try option to avoid a unique key violation is likely to waste more time and resources on average (checking for existance when a collision is rare) than just assuming a successful entry and catching that rare violation.

Unless we're talking floating point arithmetic for graphics applications or something similar, CPU cycles are cheap compared to developer time.

Cost from a time perspective carries the same argument. Relative to database queries or web service calls or file loads, normal application time will dwarf exception time. Exceptions were nearly sub-MICROsecond in 2006

I dare anybody that works in .net, to set your debugger to break on all exceptions and disable just my code and see how many exceptions are already happening that you don't even know about.

Jon Skeet says "[Exceptions are] not slow enough to make it worth avoiding them in normal use". The linked response also contains two articles from Jon on the subject. His generalized theme is that exceptions are fine and if you're experiencing them as a performance problem, there's likely a larger design issue.


p
paxdiablo

In Java, I use (in the following order):

Design-by-contract (ensuring preconditions are met before trying anything that might fail). This catches most things and I return an error code for this. Returning error codes whilst processing work (and performing rollback if needed). Exceptions, but these are used only for unexpected things.


Wouldn't it be slightly more correct to use assertions for contracts? If the contract is broken, there's nothing to save you.
@PawełHajdan, assertions are disabled by default, I believe. This has the same problem as C's assert stuff in that it won't catch problems in production code unless you run with assertions on all the time. I tend to see assertions as a way to catch problems during development, but only for stuff that will assert or not assert consistently (such as stuff with constants, not stuff with variables or anything else that can change at runtime).
And twelve years to respond to your question. I should run a help desk :-)
a
abatishchev

I dislike return codes because they cause the following pattern to mushroom throughout your code

CRetType obReturn = CODE_SUCCESS;
obReturn = CallMyFunctionWhichReturnsCodes();
if (obReturn == CODE_BLOW_UP)
{
  // bail out
  goto FunctionExit;
}

Soon a method call consisting of 4 function calls bloats up with 12 lines of error handling.. Some of which will never happen. If and switch cases abound.

Exceptions are cleaner if you use them well... to signal exceptional events .. after which the execution path cannot continue. They are often more descriptive and informational than error codes.

If you have multiple states after a method call that should be handled differently (and are not exceptional cases), use error codes or out params. Although Personaly I've found this to be rare..

I've hunted a bit about the 'performance penalty' counterargument.. more in the C++ / COM world but in the newer languages, I think the difference isn't that much. In any case, when something blows up, performance concerns are relegated to the backburner :)


T
Thomas

I wrote a blog post about this a while ago.

The performance overhead of throwing an exception should not play any role in your decision. If you're doing it right, after all, an exception is exceptional.


The linked blog post is more about look before you leap (checks) vs. easier to ask forgiveness than permission (exceptions or return codes). I've replied there with my thoughts about that issue (hint: TOCTTOU). But this question is about a different issue, namely under what conditions to use a language's exception mechanism rather than returning a value with special properties.
I completely agree. It seems I've learned a thing or two in the past nine years ;)
S
Smashery

A great piece of advice I got from The Pragmatic Programmer was something along the lines of "your program should be able to perform all its main functionality without using exceptions at all".


You're misinterpreting it. What they meant was "if your program throws exceptions in its normal flows, it's wrong". In other words "only use exceptions for exceptional things".
K
Kendall Helmstetter Gelner

I have a simple set of rules:

1) Use return codes for things you expect your immediate caller to react to.

2) Use exceptions for errors that are broader in scope, and may reasonable be expected to be handled by something many levels above the caller so that awareness of the error does not have to percolate up through many layers, making code more complex.

In Java I only ever used unchecked exceptions, checked exceptions end up just being another form of return code and in my experience the duality of what might be "returned" by a method call was generally more of a hinderance than a help.


J
Jerub

I use Exceptions in python in both Exceptional, and non-Exceptional circumstances.

It is often nice to be able to use an Exception to indicate the "request could not be performed", as opposed to returning an Error value. It means that you /always/ know that the return value is the right type, instead of arbitarily None or NotFoundSingleton or something. Here is a good example of where I prefer to use an exception handler instead of a conditional on the return value.

try:
    dataobj = datastore.fetch(obj_id)
except LookupError:
    # could not find object, create it.
    dataobj = datastore.create(....)

The side effect is that when a datastore.fetch(obj_id) is run, you never have to check if its return value is None, you get that error immediately for free. This is counter to the argument, "your program should be able to perform all its main functionality without using exceptions at all".

Here is another example of where exceptions are 'exceptionally' useful, in order to write code for dealing with the filesystem that isn't subject to race conditions.

# wrong way:
if os.path.exists(directory_to_remove):
    # race condition is here.
    os.path.rmdir(directory_to_remove)

# right way:
try: 
    os.path.rmdir(directory_to_remove)
except OSError:
    # directory didn't exist, good.
    pass

One system call instead of two, no race condition. This is a poor example because obviously this will fail with an OSError in more circumstances than the directory doesn't exist, but it's a 'good enough' solution for many tightly controlled situations.


Second example is misleading. Supposedly wrong way is wrong because os.path.rmdir code is designed to throw exception. Correct implementation in return code flow would be 'if rmdir(...)==FAILED: pass'
r
rpattabi

I believe the return codes adds to code noise. For example, I always hated the look of COM/ATL code due to return codes. There had to be an HRESULT check for every line of code. I consider the error return code is one of the bad decisions made by architects of COM. It makes it difficult to do logical grouping of the code, thus code review becomes difficult.

I am not sure about the performance comparison when there is an explicit check for the return code every line.


COM wad designed to be usable by languages that don't support exceptions.
That is a good point. It makes sense to deal with error codes for scripting languages. At least VB6 hides error code details well with encapsulating them in the Err object, which somewhat helps in cleaner code.
I disagree: VB6 only logs the last error. Combined with the infamous "on error resume next", you'll miss the source of your problem altogether, by the time you see it. Note that this is the basis of error handling in Win32 APII (see GetLastError function)
s
superjos

Exceptions are not for error handling, IMO. Exceptions are just that; exceptional events that you did not expect. Use with caution I say.

Error codes can be OK, but returning 404 or 200 from a method is bad, IMO. Use enums (.Net) instead, that makes the code more readable and easier to use for other developers. Also you don't have to maintain a table over numbers and descriptions.

Also; the try-catch-finally pattern is an anti-pattern in my book. Try-finally can be good, try-catch can also be good but try-catch-finally is never good. try-finally can often times be replaced by a "using" statement (IDispose pattern), which is better IMO. And Try-catch where you actually catch an exception you're able to handle is good, or if you do this:

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

So as long as you let the exception continue to bubble it's OK. Another example is this:

try{
    dbHasBeenUpdated = db.UpdateAll(somevalue); // true/false
}
catch (ConnectionException ex) {
    logger.Exception(ex, "Connection failed");
    dbHasBeenUpdated = false;
}

Here I actually handle the exception; what I do outside of the try-catch when the update method fails is another story, but I think my point has been made. :)

Why is then try-catch-finally an anti-pattern? Here's why:

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}
finally {
    db.Close();
}

What happens if the db object has already been closed? A new exception is thrown and it has to be handled! This is better:

try{
    using(IDatabase db = DatabaseFactory.CreateDatabase()) {
        db.UpdateAll(somevalue);
    }
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

Or, if the db object does not implement IDisposable do this:

try{
    try {
        IDatabase db = DatabaseFactory.CreateDatabase();
        db.UpdateAll(somevalue);
    }
    finally{
        db.Close();
    }
}
catch (DatabaseAlreadyClosedException dbClosedEx) {
    logger.Exception(dbClosedEx, "Database connection was closed already.");
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

That's my 2 cents anyway! :)


It will be strange if an object has .Close() but has no .Dispose()
I'm only using Close() as an example. Feel free to think of it as something else. As I state; the using pattern should be used (doh!) if available. This of course implies that the class implements IDisposable and as such you could call Dispose.
N
Nathan Long

I generally prefer return codes because they let the caller decide whether the failure is exceptional.

This approach is typical in the Elixir language.

# I care whether this succeeds. If it doesn't return :ok, raise an exception.
:ok = File.write(path, content)

# I don't care whether this succeeds. Don't check the return value.
File.write(path, content)

# This had better not succeed - the path should be read-only to me.
# If I get anything other than this error, raise an exception.
{:error, :erofs} = File.write(path, content)

# I want this to succeed but I can handle its failure
case File.write(path, content) do
  :ok => handle_success()
  error => handle_error(error)
end

People mentioned that return codes can cause you to have a lot of nested if statements, but that can be handled with better syntax. In Elixir, the with statement lets us easily separate a series of happy-path return value from any failures.

with {:ok, content} <- get_content(),
  :ok <- File.write(path, content) do
    IO.puts "everything worked, happy path code goes here"
else
  # Here we can use a single catch-all failure clause
  # or match every kind of failure individually
  # or match subsets of them however we like
  _some_error => IO.puts "one of those steps failed"
  _other_error => IO.puts "one of those steps failed"
end

Elixir still has functions that raise exceptions. Going back to my first example, I could do either of these to raise an exception if the file can't be written.

# Raises a generic MatchError because the return value isn't :ok
:ok = File.write(path, content)

# Raises a File.Error with a descriptive error message - eg, saying
# that the file is read-only
File.write!(path, content)

If I, as the caller, know that I want to raise an error if the write fails, I can choose to call File.write! instead of File.write. Or I can choose to call File.write and handle each of the possible reasons for failure differently.

Of course it's always possible to rescue an exception if we want to. But compared to handling an informative return value, it seems awkward to me. If I know that a function call can fail or even should fail, its failure isn't an exceptional case.


K
Kyle Cronin

With any decent compiler or runtime environment exceptions do not incur a significant penalty. It's more or less like a GOTO statement that jumps to the exception handler. Also, having exceptions caught by a runtime environment (like the JVM) helps isolating and fixing a bug a lot easier. I'll take a NullPointerException in Java over a segfault in C any day.


Exceptions are extremely expensive. They have to walk the stack to find potential exception handlers. This stack walk is not cheap. If a stack trace is built, it's even more expensive, because then the entire stack must be parsed.
I'm surprised that compilers can't determine where the exception will be caught at least some of the time. Plus, the fact that an exception alters the flow of code makes it easier to identify exactly where an error occurs, in my opinion, makes up for a performance penalty.
Call stacks can get extremely complex at runtime, and compilers generally don't do that kind of analysis. Even if they did, you'd still have to walk the stack to get a trace. You'd also still have to unwind the stack to deal with finally blocks and destructors for stack-allocated objects.
I do agree that the debugging benefits of exceptions often make up for the performance costs, though.
Derak Park, exception are expensive when they happen. This is the reason they should not be overused. But when they don't happen, they cost virtually nothing.
T
Trent

I prefer to use exceptions for error handling and return values (or parameters) as the normal result of a function. This gives an easy and consistent error-handling scheme and if done correctly it makes for much cleaner looking code.


D
Daniel Bruce

One of the big differences is that exceptions force you to handle an error, whereas error return codes can go unchecked.

Error return codes, if used heavily, can also cause very ugly code with lots of if tests similar to this form:

if(function(call) != ERROR_CODE) {
    do_right_thing();
}
else {
    handle_error();
}

Personally I prefer to use exceptions for errors that SHOULD or MUST be acted upon by the calling code, and only use error codes for "expected failings" where returning something is actually valid and possible.


At least in C/C++ and gcc you can give a function an attribute that will generate a warning when its return value is ignored.
phjr: While I disagree with the "return error code" pattern, your comment should perhaps become a full answer. I find it interesting enough. At the very least, it did give me an useful information.
A
Adam Bellaire

There is many reason to prefer Exceptions over return code:

Usually, for readibility, people try to minimize the number of return statement in a method. Doing so, exceptions prevent to do some extra work while in a incoorect state, and thus prevent to potentially damage more data.

Exception are generally more verbose arn more easilly extensible than return value. Assume that a method return natural number and that you use negative numbers as return code when an error occurs, if the scope of you method change and now return integers, you'll have to modify all the method calls instead of just tweaking a little bit the exception.

Exceptions allows more easilly to separate error handling of normal behaviour. They allows to ensure that some operations performs somehow as an atomic operation.


S
SCdF

I only use exceptions, no return codes. I'm talking about Java here.

The general rule I follow is if I have a method called doFoo() then it follows that if it doesn't "do foo", as it were, then something exceptional has happened and an Exception should be thrown.


R
Robert Gould

One thing I fear about exceptions is that throwing an exception will screw up code flow. For example if you do

void foo()
{
  MyPointer* p = NULL;
  try{
    p = new PointedStuff();
    //I'm a module user and  I'm doing stuff that might throw or not

  }
  catch(...)
  {
    //should I delete the pointer?
  }
}

Or even worse what if I deleted something I shouldn't have, but got thrown to catch before I did the rest of the cleanup. Throwing put a lot of weight on the poor user IMHO.


This is what the finally statement is for. But, alas, it is not in the C++ standard...
In C++ you should stick to the rule of thumb "Acquire resources in construcotor and release them in destrucotor. For this particular case auto_ptr will do just prefectly.
Thomas, you're wrong. C++ has not finally because it does not need it. It has RAII instead. The solution of Serge is one solution using RAII.
Robert, use Serge's solution, and you'll find your problem going away. Now, if you write more try/catches than throws, then (by judging your comment) perhaps you have a problem in your code. Of course, using catch(...) without a re-throw is usually bad, as it hides the error to better ignore it.
J
Jon Limjap

My general rule in the exception vs. return code argument:

Use errorcodes when you need localization/internationalization -- in .NET, you could use these errorcodes to reference a resource file which will then display the error in the appropriate language. Otherwise, use exceptions

Use exceptions only for errors that are really exceptional. If it's something that happens fairly often, either use a boolean or an enum errorcode.


There is no reason while you could not use an exception when you do l10n/i18n. Exceptions can contain localised information as well.
J
Jonathan Adelson

I don't find return codes to be less ugly than exceptions. With the exception, you have the try{} catch() {} finally {} where as with return codes you have if(){}. I used to fear exceptions for the reasons given in the post; you don't know if the pointer needs to be cleared, what have you. But I think you have the same problems when it comes to the return codes. You don't know the state of the parameters unless you know some details about the function/method in question.

Regardless, you have to handle the error if possible. You can just as easily let an exception propagate to the top level as ignore a return code and let the program segfault.

I do like the idea of returning a value (enumeration?) for results and an exception for an exceptional case.


2
2ank3th

For a language like Java, I would go with Exception because the compiler gives compile time error if exceptions are not handled.This forces the calling function to handle/throw the exceptions.

For Python, I am more conflicted. There is no compiler so it's possible that caller does not handle the exception thrown by the function leading to runtime exceptions. If you use return codes you might have unexpected behavior if not handled properly and if you use exceptions you might get runtime exceptions.


C
Carsten Führmann

There are some important aspects that remain unmentioned in this - very interesting - discussion so far.

First, it is important to note that exceptions don't apply to distributed computing, but error codes still do. Imagine communicating services distributed over multiple servers. Some communication might even be asynchronous. And the services might even use different technology stacks. Cleary, an error-handling concept is crucial here. And clearly, exceptions can't be used in this most general case, since errors have to be serialized things sent "through the cable", perhaps even in a language-neutral way. From that angle, error codes (really, error messages) are more universal than exceptions. One needs good error-message Kung Fu once one assumes a system-architect view and things need to scale.

The second point is a very different, it is about if or how a language represents discriminated unions. The question was strictly speaking about "error codes". And so were some the answers, mentioning that error codes cannot transport information as nicely as exceptions. This is true if an error code is a number. But for a fairer contrasting with exceptions, one should probably consider error values of discriminated union type. So, the return value of the callee would be of discriminated union type, and it would either be the desired happy-path value or the payload the exception would otherwise have. How often this approach is elegant enough to be preferable depends on the programming language. For example, F# has super elegant discriminated unions and the according pattern matching. In such a language it would be more seductive to avoid exceptions than in, say, C++.

The third and final point is about functional programming and pure functions. Exceptions are (in a practical way and in a theoretical-computer-science way) "side effects". In other words, functions or methods that deal with exceptions are not pure. (One practical consequence is that with exceptions one must pay attention to evaluation order.) By contrast, error values are pure, because the are just ordinary return values, with no side effects involved. Therefore, functional programmers may more likely frown upon exceptions than object-oriented programmers. (In particular if the language also has an elegant representation of the aforementioned discriminated unions.)