

Errors and Exceptions - ensiferum
https://giantfublog.wordpress.com/2015/03/11/errors-and-exceptions/

======
Animats
Well, that's one position on the subject. The Rust people prefer option 3 over
option 4. Go takes the same approach. Python prefers exceptions, and the
exception hierarchy puts (almost) all the exceptions which result from
external problems under EnvironmentError.

Much of the trouble with error returns comes from the strange C convention
that functions with return values can be called as if they didn't return a
value. This is a consequence of the original K&R PDP-11 implementation without
function prototypes, and has messed up programmers for four decades now.

RAII (resource acquisition is initialization) is popular in C++, but if
anything goes wrong in a destructor, things usually come unglued. I/O errors
on file close where close is via a destructor tend to be either ignored or
fatal. Python "with" clauses handle failure during release better. This was
well thought out in Python; exceptions in "with" clause exits are handled
rationally.

With Javascript, you get none of the above, although you can build it out of
the primitives, and there are kludges to do this.

~~~
jblow
I also prefer option 3 over option 4. It bothers me that the article
recommends exceptions without seeming to understand their drawbacks (the major
one: as soon as you use exceptions you suddenly need to apply nonlocal
reasoning everywhere in order to understand what your program will do at any
point. They turn your program from a simple local thing into a complex
nonlocal thing, which is not a good idea if you want to understand it well.)

This article seems not to have a lot in the way of new contribution; it is
just parroting the oft-repeated idea that you need exceptions to pass "error
information" up several layers of abstraction. But here is what I think about
this:

1) In this age when we are realizing strong typing is a good idea, that hidden
state is a bad idea, and that in general you should be very specific about
what is going on, why are we even conceptualizing this as "error information"?
Why instead, when we try to open a file, do we not return "all information you
might need to know in the case of opening a file" (which includes what
happened if it didn't open properly). As soon as you make that conceptual
switch, all this hand-wringing goes away. It's a non-problem. You certainly
shouldn't add heinous complications to your program to solve this non-problem.

1a) This conceptual change also helps disambiguate between what the article
calls "hard errors" and "soft errors". In portions of the code where you have
attempted an operation that might have failed, and you are not completely sure
that it didn't fail, you have the full body of "what happened" information (it
is a small struct or whatever). After the situation has been checked and you
know it is exactly what you need to be, you may drop the other information and
pass the raw file handle. At this point it is clear that these parts of the
code should only be executed if the file handle is valid, and if that is not
true, the programmer made an error. This is analogous to the situation with
nullable and non-nullable pointers (in some languages you would even use the
same mechanisms to deal with null pointers and invalid file handles, etc, but
I am not sure this is really helpful.)

2) If one insists on not making the simplifying leap from (1), well, maybe the
other problem is that you have so many layers. If you didn't have so much
glue, your code would be simpler and easier to deal with, and it would run
faster, and you wouldn't be so worried about needing to pass lots of context
information up several layers between modules, because those situations don't
really arise.

~~~
barrkel
_In this age when we are realizing strong typing is a good idea, that hidden
state is a bad idea, and that in general you should be very specific about
what is going on_

Java tried to be typesafe about errors, about not obscuring the state, and it
was a dreadful idea. The way code fails is a function of its implementation;
communicating high quality error information is fundamentally an abstraction
violation, but a strongly typed facade that forces you to rewrap all your
error cases in some other type just obscures the underlying problem.

Strongly typed error information also breaks composability. How do you write a
'map' function that can accept a callback, where the callback may want to
communicate back one of several distinct kinds of error information? With
unchecked exceptions, the way forward is clear. Without them, you either need
to write a lot of boilerplate to unify your error types in some kind of
container, or rely on subtyping to fit them into a single type and live with
the lack of strong typing via another route.

Dynamically typed programming languages improve usability over statically
typed languages particularly when a precise description of the types used in
the static case is difficult or tedious to express. Error information, and its
intrinsic implementation-dependent content, is one of the strongest instances
of this.

~~~
dllthomas
You could have checked exceptions with more flexibility than Java had, and it
might work out well (or might break down for other reasons).

 _" How do you write a 'map' function that can accept a callback, where the
callback may want to communicate back one of several distinct kinds of
error?"_

With Java style checked exceptions, you don't, and that's for sure a problem.

However, one approach would be to say that map is polymorphic in the
exceptions that may be raised from within it. But that set is not entirely
unbounded - it is precisely the set that can be raised by its function
argument, unioned with any that might be raised during traversal of its
container argument.

------
nyir
And option number five, conditions + restarts,
[http://www.lispworks.com/documentation/lw61/CLHS/Body/09_a.h...](http://www.lispworks.com/documentation/lw61/CLHS/Body/09_a.htm),
which is a bit like more formalised callbacks / exceptions depending on usage.
Restarts are a great thing for both interactive (using a "continue" restart if
e.g. something can be safely skipped) and programmatic usage (to customise an
algorithm where you control from the outside whether a restart should be
invoked in case of a "soft" exception/condition).

------
bluetomcat
He talks about "soft" errors as if the only way to handle them is to propagate
them up to the UI layer, displaying a message.

In many situations the error handling affects the code paths at a more
profound level. Something might be considered an error in the callee, but at
the same time it might be an acceptable result in the caller, for which a
well-defined path exists. Throwing an exception in such situations is just
disconnecting the faulting code from the error handling code.

~~~
einrealist
The perspective is about layered code and reuse. So what is wrong with
delegation? My file access library should not know that it is linked in an
application with an UI. Throwing an exception just delegates the issue to the
code which depends on me. Whether the IO exception is really a problem can
only be decided by the user of my library.

------
Const-me
Exceptions only work well in managed languages like C#, Java, Python.

In C++ they don’t.

In C++, error codes FTW. On Windows you usually return HRESULT, call
FAILED/SUCEEDED macros to check, call OS-provided FormatMessage API to format
an error (the messages are of course localized). If you’re using some 3rd
party library that uses its own error codes, it’s usually trivial to pack them
in HRESULT when failed, check HRESULT facility when formatting.

In C++, exceptions have fatal disadvantage: they don’t work across modules.
Two reasons: (1) C++ exceptions aren’t binary compatible across compilers (2)
memory management isn’t compatible across compilers either, if some module has
called new/malloc, the same module must call delete/free

~~~
stinos
Don't really agree, this seems too general? It's a matter of preference, what
level you're working on and of what is possible. For example you're writing
C++ which is going to be wrapped in a C-style api then yes you are going to
need those error codes. If you are writing an application using some C++ api
with well-defined exceptions then using them might lead to much nicer code.
Consider some function in main() which takes care of a ton of initialization;
I just happen to prefer

    
    
      try
      {
        MethodA();
        MethodB();
        MethodC();
      }
      catch( const FooException& e )
      {
        //show e
        return 1;
      }
      catch( const BarException& e )
      {
        //show e
        return 1;
      }
    

over

    
    
      auto resulta = MethodA(); 
      if( FAILED( resulta ) )
      {
        //show result
        return 1;
      }
      auto resultb = MethodB(); 
      if( FAILED( resultb ) )
      {
        //show result
        return 1;
      }
      auto resultc = MethodC(); 
      if( FAILED( resultc ) )
      {
        //show result
        return 1;
      }
    
    

_they don’t work across modules_

A problem which fades away if you build all code using the same compiler and
build settings, which is a good idea anyway (there's not much which works
across modules when mixing e.g. debug and release builds). I've written a lot
of C++, and misbehaving across modules is like the _one_ thing I didn't have
problems with :]

~~~
kabdib
Go read one of Herb Sutter's "Exceptional C++" books. Okay, just read the
_first_ one. If you don't close the book and reflect, "I am never writing this
shit," Herb didn't do his job.

Writing exception-safe code in C++ is very hard.

Depending on another module's authors to get their C++ exception handling
right is the road to madness.

In the example above, the FAILED clauses clearly handle all the failures. In
the exception handling example it is not clear if all the failures are caught,
or if they are more that are meant to be caught at higher levels, or perhaps
exceptions were forgotten. It becomes very difficult to reason about control
flow because exceptions distribute it across the program and over time.
Someone might add another exception: Oops, you didn't handle
FileNotFoundException and your program bombs. You add a new exception that
seems reasonable, but you wind up changing a hundred places in your source
code (... and your callers! hope you have good customer support).

Exceptions are exceptional, they should not be used to return indications of
failure. Exceptions should be used when it's lights-out important that
something Really Bad be handled or the program will be killed. Most C++
systems I've worked with catch an exception near the top, do some diagnostics,
and either tell the use "wups" or attempt to restart.

A "FileNotFound" exception from an open() function is just colossally stupid.
I don't have a nicer way of saying that. If I saw code with this pattern in a
project that I was on, I would remove it and make sure it didn't happen again.

~~~
exceptthat
_If you don 't close the book and reflect, "I am never writing this shit,"
Herb didn't do his job._

The same Herb who said _Prefer to Use Exceptions to Report Errors_? (which
makes no sense btw, just like saying to never ever use them)

 _Depending on another module 's authors to get their C++ exception handling
right is the road to madness._

Wait, so we should just forget about STL/boost/.. and reinvent all wheels? Or
maybe just all code - depending on another module's authors to get their error
code handling right, as in not ignoring all of them, is also madness.

 _You add a new exception that seems reasonable, but you wind up changing a
hundred places in your source code (... and your callers! hope you have good
customer support)._

That is just not how proper code using exceptions gets written, ever. Anyone
can make up similar horror stories for error codes (you are checking if HR is
FILE_NOT_FOUND or FILE_NOT_ACCESSIBLE but suddenly the author changed the name
into FOOFILE_NOT_FOUND and now you wind up changing a hundred places in your
source code) Callers know what exceptions they can handle (if they don't the
code is crap anyway) and handle those. If a caller wants to provide a strong
guarantee the caller catches everything and nothing is forgotten. Everything
else is truly exceptional and bubbles up, eventually to the top like all most
C++ systems you saw.

~~~
dllthomas
_" you are checking if HR is FILE_NOT_FOUND or FILE_NOT_ACCESSIBLE but
suddenly the author changed the name into FOOFILE_NOT_FOUND and now you wind
up changing a hundred places in your source code"_

Not weighing in on the broader discussion (in this comment), but more
languages support exhaustiveness checking of case statements than of
exceptions.

------
simula67
This article caused me to lose my faith in exceptions :
[http://ptgmedia.pearsoncmg.com/images/020163371x/supplements...](http://ptgmedia.pearsoncmg.com/images/020163371x/supplements/Exception_Handling_Article.html)

~~~
Quiark
That article is extremely outdated and by today standards quite ill-informed.
Many of its arguments could be equally made for error codes.

Try some of these instead:

    
    
        http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C
        www.boost.org/community/exception_safety.html
        https://github.com/Quiark/CppExceptDetails
        http://www.boost.org/community/error_handling.html

------
asgard1024
Regarding what he calls "soft errors" (a better name would be environmental
errors, I think), it's unfortunate that option 2 (error handlers) is so often
neglected by programming language designers (for example Code Complete doesn't
mention option 2 at all).

In my view, just like option 4 (exceptions) is more general than option 3
(return values), also option 2 is more general than option 4. The biggest
advantage of 2 compared to 4 is that recovery is much easier. However, more
generality also unfortunately means more complexity (both for programmer and
runtime). That's why many people here prefer 3 to 4 or 2, and neither one of
them is really better than the other.

Common Lisp is a good example of language that has good support for all three.
It can return multiple values which facilitates option 3, it has usual
exceptions as option 4, and most importantly, it also has signals and restarts
that serve as option 2.

I would classify what he calls hard errors into two categories. One is
inconsistent input and the other internal (logic) error. This depends on level
of view, if you are looking only at one module or a bigger whole (inconsistent
input into one module from the other can be considered internal error in the
whole).

Now, I don't think inconsistent input should be dealt with asserts. If it's
worth checking the inconsistent input at the module boundary, do it and return
an error (in any of the three ways mentioned above). The caller should decide
whether or not this is a logic error (it may also be bad input from the user),
and how to handle the failure.

While I agree with the statement that on logic errors one should generally
fail hard, sometimes it's not desirable, for instance in server you may want
to just restart the wrong thread instead of shutting down the whole server.

Finally, I think asserts are very much underrated in the current development
practices, compared to tests. I wish the effort spent on testing frameworks
would be spent on frameworks that would let you add lots of asserts into the
code and turn them on/off as needed (for example based on required tradeoff
between reliability and performance).

------
ensiferum
Some interesting discussion here.

First of all it seems that some people are still having a bit muddled vision
about _what_ is an exceptional error/condition. Of course you will need to
first define what constitutes an exceptional situation in your application and
its domain. The blog post used a file opening as an example. That example
implied that in that software, in that particular context we have a
requirement that our file _must_ be opened. If for some reason it cannot be
opened we have grounds for an exceptional condition. When we have this
condition we then choose some particular method/means for how to indicate the
situation and how to deal with it.

We can also demonstrate this the otherway around. Someone mentioned as an
example something like a (web)server. Obviously in that application's domain
not being able to open and serve some file is just a part of the normal flow.
Clients may make request to resources that do not exist. So in that example
that would not be an exceptional circustamce but the "FILE NOT FOUND"
condition is just part of the normal conditional flow of the program.

However in that same server we might be for example reading the initial
configuration file when the program is started and if we fail to open the file
then we might have grounds for an exceptional situation.

So to sum this up, what constitutes an exception situtation is all up to the
domain of the program. The blog post discussed methods for dealing with that
situation. This is an important point to realize.

Finally a note on assert, yes there are cases when you can't hard fail and
core dump on assert as much as you would like to. For example when
implementing API's that define error return values for "hard failure"
conditions. Whether this is sensible practice and makes the software world
better or worse is a matter for another discussion.

------
masklinn
It's a bit sad (and an indictment to "modern" languages) that option 5
"Conditions" (resumable/non-unwinding exceptions) is not mentioned.

~~~
qznc
How is that different to option 2?

~~~
masklinn
It has nothing in common with 2. It's an extension of 4 where the eventual
(dynamically scoped) handler is executed on the non-unwound stack and may
unwind the stack (same as an exception), perform an action on the non-unwound
system (e.g. resume, resume with a value provided, resume with a restart
specified, repeat, etc…) or dynamically opt to resume looking up handlers.

~~~
asgard1024
I have to disagree. Historically, conditions and restarts came from Multics
and PL/I. There is also similar thing in IBM z/OS, descendant of MVS (ESTAE
recovery routines). When ESTAE is invoked, you can decide where you want to
recover (since it's assembler you may unwind stack if you have one), or you
can "percolate", which means to pass processing on the next error handler.
Unix signals is probably another descendant. So in the 1960s, recovery by
error handler (the option 2) was very common thing. It only got a bit
forgotten later due to them not being properly supported in C, so it didn't
get into C++ and Java due to cultural disconnect.

------
losvedir
No discussion of errors and exceptions could be complete without including
Erlang (/OTP)! How does its philosophy of fail fast and rely on your
supervisor to restart fit into 1-4? I've been dabbling in Elixir lately, and I
really like how easy it makes writing the "happy path", while still robustly
handling errors.

------
gorena
I find anything but a Result<T, E> ADT to be a poor solution. Errors shouldn't
have language-level support (exceptions), and error codes are just a poor
implementation of a result ADT - they cannot be enforced by the type system.

Result is a monad, so it doesn't require an alternative code path. It's the
cleanest and safest.

"Exceptions" should always be for fatal, irrecoverable errors (array index out
of bounds). I'm okay with having them, but I don't think languages should
support "catch".

~~~
cousin_it
How about using an algebraic effect system? That's sort of a general purpose
alternative to monads that also doesn't require an extra code path, and adds a
bit of theoretical niceness. For example, if your code can throw two different
exceptions, composing two monads is order-sensitive (because the monad
interface is in some sense too general and forgets too much), while algebraic
effects always commute with each other.

~~~
gorena
True. I could rephrase as "the possibility of errors is just another type, and
is best treated as such"?

------
MishraAnurag
Great post. Soft errors have a different meaning altogether in electronics.
It's an interesting read.

[https://en.m.wikipedia.org/wiki/Soft_error](https://en.m.wikipedia.org/wiki/Soft_error)

