

Exceptions (2003) - abl
http://www.joelonsoftware.com/items/2003/10/13.html

======
beagle3
Everyone here seems to agree that "exceptions are for exceptional conditions".
The problem is that when you get down to details, there is disagreement about
what exactly is an "exceptional condition".

e.g. - you are trying to open a file for reading. The file does not exist. Is
this exceptional? That depends on context, but the function that opens the
file, being in an independent library, is usually designed without this
context.

If it does throw an exception, some people complain that "of course it'e
expected that file won't be there sometimes! that's not exceptional".

If it doesn't throw an exception, some people complain that "we tried to open
a file, but didn't succeed, of course that's an exception". But if you want to
avoid an exception in this case, you'll need to check for existence before
opening (LBYL "look-before-you-leap"), and get into a race condition (TOCTOU
"time-of-check,time-of-use"), which is really bad.

So it very often happens that you are forced by your ecosystem to use
exceptions for normal control flow. Claims that you can only use it for
"exceptional / unexpected" tend to be incompatible with a project in which you
do not develop/control all of the library you use to your strict standard of
exceptionalness.

~~~
mindslight
Vague value judgments ("only for exceptional conditions") are the inevitable
result of a failure to reason.

The semantic function of exceptions is just a way for summing additional
values onto the return type of a function because it has results that are not
contained within the primary type. In this way, they are a more general and
better typed version of NULL (which has it's places - contrary to the modern
dogma, these sort of features are needed due to inherent complexity). The
standard ways of attempting to avoid this are to either use sentinel values
that exist in your standard return type like fd == -1 for an error (thereby
making your program less typed), or to create a top-level sum type for every
aggregated function return type (cluttering your program with nominal types).
Multiple values make the most sense, but those are ad-hoc _product_ types, so
you're eschewing the type system in favor of informal invariants.

The syntactic function of exceptions is to avoid constantly repeating (check
for error, return error), which often leads to the poor practices of ignoring
errors or calling a global exit(). One goal of programming languages is to
automate, so it makes sense to capture this oft-repeated pattern. But problems
arise when people end up forgetting that every function can have a possible
return immediately following it.

It seems some syntactic middleground is needed to signal the complete return
type of a function definition, and the possibility that a given function call
may quick-return. Honestly (and I hate to say it), but Java probably started
down the right track with checked exceptions, but being a B&D language it
ended up being waaaay too verbose. And lacking a way to aggregate types along
anything but the baked-in hierarchy, people fell into using generic and
uninformative 'throws Exception'. And open types make it so there's little
point trying to enumerate exhaustive causes. But that doesn't mean that one
can't start with the idea of non-silent but syntactically lightweight
exceptions and come up with something that avoids a lot of the pitfalls.

~~~
stormbrew
I'm really fond of the idea of Nullable types (where you have to unwrap them
by checking if they're null or errors to get the value). I don't think any
non-functional language other than Rust has attempted to work this into the
way they work yet, but it strikes me as a powerful mechanism that could also
allow for deferring error handling to the site most capable of dealing with
it.

~~~
aaronbee
Scala has the Try [0] and Either [1] types that can be used to achieve this.
However, I believe Scala also allows any type to be NULL. In Rust there is no
NULL so you don't have to worry if a value is ever NULL unless it is
explicitly wrapped in an option instance.

[0] [http://www.scala-
lang.org/api/current/index.html#scala.util....](http://www.scala-
lang.org/api/current/index.html#scala.util.Try) [1] [http://www.scala-
lang.org/api/current/index.html#scala.util....](http://www.scala-
lang.org/api/current/index.html#scala.util.Either)

------
betterunix
I think Common Lisp has a good approach with its restarts system. I try to
write something to a file but there is not a enough disk space? How about
telling the user, then invoking a restart when the user says, "There is more
space available!" and continuing execution as if nothing went wrong? The
problem with exceptions is that there is no way to recover from them in most
languages, because the exception handler is found by unwinding the stack.

What I do not like about the "check return values" approach is:

1\. It means that client code must understand how to handle error conditions.
No disk space? Well whoever called the top-level function that invoked _write_
needs to figure out what to do if there is any chance of recovery. It is a
maintenance headache that can quickly accumulate bugs.

2\. In both Java and C++ there are functions that cannot return values:
constructors, and in C++ destructors. No, it is not acceptable for a program
to crash just because a constructor call failed. No, it is not any better to
have every class have a special variable that indicates that the constructor
failed. No, having empty constructors is not the answer, and it is certainly
not going to help with destructor calls (the C++ standard library actually
requires some destructors to silently fail because of the issues with error
reporting).

~~~
asgard1024
Restarts are perfect! This bothered me greatly when I read Code Complete. It
doesn't discuss error handlers (which restarts are a form of) in error
handling chapter at all.

I really wish more people knew about this option. Maybe it would even get into
mainstream languages.

------
BIackSwan
Exactly fits the philosophy of Google Go - [http://blog.golang.org/error-
handling-and-go](http://blog.golang.org/error-handling-and-go)

I think his point of there being easy syntax for multiple returns is
critically important to make this sort of error handling non annoying - which
Go does remarkably well.

I think this factor has a lot to contribute to the fact that you get the warm
fuzzy feeling after your code compiles. You feel confident that you have
already handled all the error cases (that you care about) in your code.

EDIT - Obligatory nitpick accepted. ;)

~~~
chimeracoder
<nitpick type="obligatory">The name of the language is Go, not "Google
Golang"</nitpick>

That said, this was, for me, the single weirdest thing to get used to when
starting to program in Go, coming from a mostly Python/Java exception-style
background. (I imagine it's easier if you're a C programmer).

However, once I got into the swing of it, I realized I really, really like
Go's error handling, and I can't imagine going back to Python's exceptions
voluntarily. Returning errors makes code much more readable, and it also
reduces the risk of code suddenly failing because of an exception that got
thrown somewhere that you can't even find easily.

Go also gets a more subtle point correct: errors are _interfaces_ , not types,
which means that you can use any type as an error, as long as it supports the
"Error() string" method. This is irrelevant 99% of the time, but I've seen a
few pieces of code which utilize this feature very effectively.

~~~
mdkess
I have to say, I much prefer people referring to it as Golang, otherwise it is
impossible to search for.

~~~
lrichardson
this is honestly the most annoying thing about go for me. Really hard to do a
quick google search for an answer to something!

~~~
d0m
Try "Django" with that new "Django Unchained" movie ;-)

~~~
trobertson
For all these language issues, I generally preface the query with the language
name. "Python3 Django", "Haskell Quickcheck", "C++ Boost", etc. It makes
things so much easier for the search engine to give me Good Results (tm) when
it has that extra bit of context.

------
stan_rogers
The discussion around that time was interesting (though distributed around the
blogosphere); Ned Batchelder had recently argued the opposite (and updated the
argument specifically to take Joel's article into account)[0][1]. It was at
about that same time that Damien Katz was becoming firmly convinced that
Erlang and crash-only behaviour would be the way to go in designing
CouchDB[2][3]. (Both Damien and Ned were at Iris/Lotus working on Notes and
Domino, and I was a Domino dev at the time.)

[0]
[http://nedbatchelder.com/blog/200310/joel_on_exceptions.html](http://nedbatchelder.com/blog/200310/joel_on_exceptions.html)
[1] [http://nedbatchelder.com/text/exceptions-vs-
status.html](http://nedbatchelder.com/text/exceptions-vs-status.html) [2]
[http://damienkatz.net/2004/08/crash-only-
software.html](http://damienkatz.net/2004/08/crash-only-software.html) [3]
[http://damienkatz.net/2004/09/crash-only-software-
revisited....](http://damienkatz.net/2004/09/crash-only-software-
revisited.html)

------
olegp
If you're catching exceptions all over the place, or worse using them for flow
control as part of normal operations, you're doing it wrong. Exceptions should
indicate a major error that you can't easily recover from and as such should
be caught and logged at the top of the stack, i.e. the main thread run method
or request handler.

When used that way, they give you very useful information as to what went
wrong and where, while making your program more robust and resilient to
errors. We've found this to be the case time after time at
[https://starthq.com](https://starthq.com), which runs on Node but uses fibers
via [https://github.com/olegp/common-node](https://github.com/olegp/common-
node).

~~~
betterunix
"Exceptions should indicate a major error that you can't easily recover from"

Maybe in Java and C++, but in Common Lisp we have _restarts_ that allow you to
recover from an exception. I like to use the example of attempting to write to
a file when the disk is full, because:

1\. It is possible to recover from the exception (e.g. ask the user to delete
some files)

2\. It makes no sense for the I/O library to do _all_ the things needed to
recover

3\. It is a maintenance headache for client code to do _all_ the things needed
to recover

With restarts things would look like this: the I/O library would set up a
restart for _write_ that would retry the operation, the client code would
catch the exception, prompt the user to free some space, and when the user
indicates the space is free the restart is invoked. The I/O library knows the
right way to restart the operation, and client code knows whether or not that
should happen, and you get code that does not just quit over a disk being
full.

------
mwsherman
I am not a huge fan of exceptions per se, but it’s important to understand
that they are a heuristic for minimizing ‘worry’ about things that are
unlikely. I am not saying it’s a good thing, I am saying it’s the way people
naturally work.

Let’s say there is a function that’s 20 lines long, and if you did a thorough
analysis of possible error conditions, regardless of likelihood, you might
come up with 50 or more.

We are not going to write code to address all 50 possibilities from the start.
Instead, we are going _de facto_ to wait and see what fails in the real world,
and address them as we discover them, because we value our time. We make an
economic distinction between a 1-in-1,000 problem, and a 1-in-1,000,000,000
problem.

Is this un-robust? Yes. Are we allowing exceptions to be a control-flow catch-
all? Yes. Would a Go-like approach of simply returning error conditions reduce
bugs? Probably. But it’s important to recognize programmers’ ‘revealed
preference’ for exceptions.

~~~
crazygringo
50 error conditions in a 20-line function doesn't really sound even remotely
realistic. Probably only 5 of the 20 lines are actually calling functions that
might return errors, and in most cases, we don't care what _type_ of error is
being returned.

So if we're talking about 5 error-checks in 20 lines, then yes, we _absolutely
should write code to address them from the start_.

I mean, I can understand not dealing with errors from memory allocation
failing, or even possibly failure to write bytes to disk, depending on the
situation (e.g., if those fail, you've got bigger problems to worry about than
your error handling -- and it's not like exceptions are probably helping you
to recover anyways).

But for stuff like network communication, writing to databases, etc., you had
definitely better be addressing all error possibilities from the start,
because these things fail _all the time_.

~~~
mattmanser
Nonsense.

Is the network up?

Is the connection to SQL up?

Did someone just turn off the SQL machine half way through the query?

Can we find the server?

Are there any rows?

Did the SQL compile?

Do I have rights on this table?

Have you just terminated me as a result of a deadlock?

Did you return a Null when I was expecting a value?

Did you return a float when I was expecting an int?

Did my value just overflow?

Did you just return 0 and I tried to use it in division?

Did I just try to access the session but some other idiot clear it?

Did I just try to call a method on an object that is in fact null?

And that's all possible in a three liner off the top of my head. I'm sure
there's plenty more than that that are possible! I didn't even start on the
file ones...

~~~
polyfractal
Functions should be scoped appropriately so that they only deal with one thing
at a time. A function that checks for network connectivity isn't going to be
checking for missing rows. Those are different problems, and should be handled
by different functions.

Using your example, we have a function that queries a database. It will be
given a valid database connection, and return the result of the query.

There is no "valid database connection" logic, since that is handled
elsewhere. There is no result validation logic, since that is handled
upstream. This function only cares about A) querying and B) returning a value
(possibly null). The only exceptions that is handles is when something
specific to it's domain goes wrong - for example, unauthorized access to a
table. That is an error that is above networking (the connection worked fine)
but clearly not a data validation problem (no data), so we handle the
exception here.

If you find yourself throwing exceptions "across problem domains", that's a
good indicator that your functions are doing too much.

~~~
mynameisme
You can't just assume that a valid database connection will remain valid the
entire time you're using it. The network or server is free to go down at any
time. In practice, this rarely happens, it's an exceptional condition.
Exceptions are the best way to model these kind of errors.

~~~
polyfractal
Sure, but the query goes through the layer of functions that actually deals
with the database connection. If an error occurs, that layer is responsible
and the "higher" layers simply pass on the exception that is thrown.

Point is: the function that deals with database response should never be
responsible for dealing with database connection errors.

------
dsego
This is a problem of architecture. Should you mix error handling with
application logic? You have to handle errors somewhere, but they pollute the
otherwise clean problem-solving code. One solution is to just let the
exceptions bubble up and the other is to handle them immediately. If you let
them bubble up, you can have a separate module that can do the right thing,
notify the user, restart the app or something else. This way, the error
handling is somewhat cleanly separated into its own thing. Sometimes you want
to handle errors immediately, because only the code where the exception
happens, knows how to deal with it. This is where return codes might be
better. A lot of it depends on the desired behaviour. Sometimes you want the
software to immediately exit or restart if there is a critical exception,
sometimes you can safely ignore errors if real-time experience is more
important. Sometimes all you want is to log the exception and/or notify the
user. Does anyone have experience with aspect-oriented programming and does it
help solve any of these problems?

------
Piskvorrr
"Monday, October 13, 2003" \- more like "the old new GOTO"; but apart from the
out-of-the-blue rant about PHP (version 4, I guess?) at the bottom, this still
seems applicable.

~~~
rvkennedy
His argument is really against using exceptions as part of normal operation,
which I agree with. I've noticed when writing plugins to big monolithic
programs like 3DS Max and game engines that these tend to throw and catch
exceptions as a matter of course, which is a pain when you're trying to debug
your own plugin code.

An exception _should_ mean "normal operation cannot continue", and should
signify a bug in your code. As such, it is an exceptionally good software
development tool.

~~~
smtddr
I got a question. Do you think it's okay use exceptions to end what would be
otherwise an infinite loop? Python example:

    
    
      data = ""
      socket.settimeout(1.5)
      while 1:
        try:
          data += socket.recv(1024)
        except Exception,e:
          store_data(data)
          break
    

If this is not okay, what would you change?

(And yes, I know there are edge'ish cases were I'd miss some data here)

~~~
btilly
This is NOT OK. You're swallowing all exceptions and assuming that they are
your special case.

The fix is to have a specific nonambiguous name for your exception, so that
other error conditions still work properly. As examples consider the
StopIteration and GeneratorExit exceptions from Python's standard library.
(See
[http://docs.python.org/2/library/exceptions.html](http://docs.python.org/2/library/exceptions.html)
for a list of built-in exceptions.)

------
jeffdavis
I think there is reasonable consensus about some software engineering best
practices:

1\. Avoid action-at-a-distance and side-effects that are hard to reason about.

2\. Use immutable objects and values rather than references and pointers.

3\. Avoid intricate control flow with many branches.

4\. Greater isolation of processes/threads (actor model).

5\. Use systems and platforms with simple and strong guarantees (e.g. ACID)
that are easy to reason about. Special cases and nuances to the underlying
platform should be avoided.

6\. Use tools with good support for static analysis (e.g. a good type system
with a compiler that can understand it).

Exceptions seem to violate #1, #3, and #6.

These rules only apply to software engineering; that is, the reliability,
robustness, long-term maintainability, and total project development cost
(including maintenance and support).

Of course, there are other considerations, such as: performance, the time to
achieve the minimum viable product, how much developers like the tools, how
"hackable" it is, or utility for research purposes. These other concerns may
be a good reason to violate the above rules.

------
zzzcpan
Sure, exceptions are bad most of the time. But sometimes they are really
useful, like in heavily recursive code, i.e. recursive parsers. Catching
exceptions in a single top level function and throwing in every other one
makes code much cleaner, since you don't have to propagate and handle errors
on every function call and you have single exit point on top level function
anyway.

------
dicroce
I don't use exceptions for normal flow. I use them for errors.

While it is true that exceptions create innumerable code paths through a
function, RAII makes that manageable. If you're not taking advantage of RAII
in your C++ code, you might as well be writing C code.

------
mamcx
The advantage of exceptions is the ability to pass back the error and make
mandatory dealing it (even if ignoring).

Exist a way to have both styles, cleanly?

I have thinking in how could look a language where objects, like in UNIX
pipes, have stdout & stderr, and if stderr is consumed (returning codes) the
exception is handled, but if stderr is not consumed, then raise it?

------
michaelwww
_> [as an alternative to exceptions] "It is true that what should be a simple
3 line program often blossoms to 48 lines when you put in good error checking,
but that's life _

Was he being serious? I'd rather not wade through all that error checking
cruft to see normal flow. Exceptions (or goto) don't create buggy hard to read
programs, people do.

~~~
crazygringo
Except that error handling _is_ part of normal flow.

Wishing it wasn't, or just laziness, leads to bugs. It's not cruft, it's
integral. That's the whole point.

~~~
michaelwww
I disagree. Building an error checking scaffolding around every possible and
unlikely error obscures the expected flow of control. If I checked for every
possible error condition before I started the engine of my car in the morning
it would take me an hour to get out of the driveway. I'd much rather have the
car tell me when something was wrong and not start. If the system you are
using already throws exceptions, you might as well use them too.

------
FreeFull
Some Haskellers treat exceptions as something that you only use if the program
has entered an unrecoverable condition and needs to crash.

~~~
ghc
I don't find this true at all. Haskell exceptions don't look like exceptions
in other languages, so it's easy to get hung up on the fact that _error_ is
generally used only for unrecoverable errors. But that's why it's called
_error_ , not _exception_. In Haskell, Either and Maybe are used where I would
normally use try/except in Python.

The only exception to this that I've found is that error is something you may
need to deal with in the IO monad, where in Python it would still be handled
via exceptions.

~~~
FreeFull
Well, I didn't think of Maybe and Either as exceptions, but rather considered
undefined/error. The exceptions from Control.Exception are probably the
closest to exceptions in other languages, and I think those are built on
error.

------
rwallace
Most of the discussion about exceptions tries to think about it in abstract
terms, which doesn't work. The point of exceptions is a very concrete one:
often the code that runs into an error and the code that handles it are
separated by ten levels of function calls. Without exceptions, the logic for
detecting and passing on the error has to be duplicated in every function in
every one of those levels.

Exceptions are arguably the one feature of C++ that isn't just syntax sugar
over C, the one feature that makes the language fundamentally more expressive:
they reduce a certain kind of code complexity from O(N) to O(1).

------
detrino
Forcing a function call to handle an exceptional condition when it doesn't
know how doesn't really add anything but bloat to your source code (violating
DRY). It's interesting the author mentions goto, because without exceptions
goto is often the most reasonable but more error prone way to handle cleanup.
C++ solves this using RAII, if you need something to run before the end of the
scope, stick it in a destructor. This is very easy and far more composable.

------
samatman
While I'm not familiar with the Erlang paradigm that inspires it, the dire[1]
library in Clojure is designed to decomplect error handling from code that may
generate an error. I like this approach a lot.

[1]:
[https://github.com/MichaelDrogalis/dire](https://github.com/MichaelDrogalis/dire)

------
mtdewcmu
> This is ugly and annoying but it's better than getting magic unexpected
> gotos sprinkled throughout your code at unpredictable places.

The proper analogy would be not GOTO, but COMEFROM, no? A catch block is
basically a COMEFROM. :)

------
alexlamsl
[http://nodejsreactions.tumblr.com/post/59877403633](http://nodejsreactions.tumblr.com/post/59877403633)

------
shizamhn
As a side note, at least in Objective C, blocks are a great way to return
multiple values (including errors) and deal with them immedately.

------
spo81rty
You can make methods easily return multiple values in C# with Tuples. They are
very handy.

~~~
MichaelGG
For extremely low values of "easily". C#'s lack of pattern matching and tuple
syntax makes dealing with tuples in C# a complete pain in the ass. It's ugly,
annoying, code, in general.

P.S. You can do this in pretty much any language. Just define pair, triplet,
etc.

------
hacknat
This old gem, huh?

