
On Rigorous Error Handling - rumcajz
http://250bpm.com/blog:140
======
fmap
> When possible errors are part of the function specification, on the other
> hand, we are almost OK.

This is the single best piece of advice in this article. The second thing you
have to document is the postcondition in case of an error - what state is the
program left in?

With both a normal and an error postcondition you can fully specify your
program. Like the author, I'm convinced that most of the pain with error
handling stems from programmers ignoring the consequences of an error. That's
the reason why approaches that force you to deal with errors explicitly (Maybe
a, Result e a, etc.) end up being more robust. Otherwise, a part of the
program just ends up missing.

However, from a theoretical perspective, exceptions are superior. The reason
is that the error postcondition really does represent a non-local exit. Just
like an ordinary return statement, it should be implemented as one instead of
forcing programmers to walk the stack by hand. The latter is both less
efficient and more error prone. Additionally, resource management must be
integrated with error handling anyway and exceptions provide a clean
opportunity to connect the two. This is one of the things that C++ gets right.

~~~
panic
When a database transaction fails, it rolls back to the state before the
transaction. Exceptions ought to work like this too. Then you wouldn’t have to
think about all the places an exception could be thrown. A try-catch block
would either completely succeed, following the well-tested success path, or
completely fail, leaving the program in its original state.

~~~
dllthomas
You can't un-fire the missiles.

This might be a great approach for some (plausibly very large) subset of
cases, but it can't handle everything.

~~~
gpderetta
You can wait to fire them until commit time though.

Also abort sequences are a thing so you can kinda-sorta unfire them (talk
about compensating sequence!).

~~~
dllthomas
> You can wait to fire them until commit time though.

Not in a way that truly solves the problem. Any time you are coordinating
multiple actions that are irreversible and may fail, you'll need some contract
other than "either your transaction exceeds or everything is rolled back."

------
makecheck
Error-handling code is definitely poorly-tested in my experience across many
code bases. Although printf()-style logging has advantages, a huge
disadvantage is that it tends to be the _reason_ error-handling code fails:
something meant to write a simple log message gets the format/type wrong and
an error condition turns into a crash or obscure corruption. In fact, logging
code that was _once_ correct can _become_ wrong if the target variable type
changes. This is why I love Python format-strings with “{}”, a.k.a. the “just
do the right thing here” syntax.

Generally the advice of “pick a few failure types and stick to them” is
exactly right. You not only encourage error handling to take place but that
code is likely to _remain_ correct/complete over time.

~~~
ben-schaaf
An issue that can still happen with python's string formatting is that you can
get the number of arguments/`{}` wrong. D (other languages too, probably) has
compile-time format string, as well as automatic string conversion:
`format!"%s"(2)`. Giving the wrong format string/argument types fails at
compile time. Some C/C++ compilers also automatically check this for printf.
Though they can still fail if the actual output writing fails, ie. stdout is
closed or doesn't exist.

~~~
teddyh
> _An issue that can still happen with python 's string formatting is that you
> can get the number of arguments/`{}` wrong._

Easily avoided by using the latest Python feature, added in 3.6: Formatted
string literals, a.k.a. f-strings¹. Instead of using

    
    
      "foo {} baz".format(bar)
    

you use

    
    
      f"foo {bar} baz"
    

1\.
[https://www.python.org/dev/peps/pep-0498](https://www.python.org/dev/peps/pep-0498)

------
skybrian
Provided that you have a way of simulating an error, you can test the
corresponding error-handling path. Then these paths will get executed whenever
you run your test suite.

A coverage tool can be used to find any error handling code that wasn't
tested. (But it won't help you find error handling that's missing altogether.)

------
ken
> Programmers want to implement new features. Writing error handling is just
> an annoyance that slows them down.

For me, personally, this is backwards. As a programmer, I want to write error
handling (especially for infrastructure), because it means I'll be able to
work more quickly later. I won't have to debug through all these abstraction
layers. It's the _manager_ who always says "It (the demo = happy path) looks
good, so it's time to move on to the next feature".

------
sfilargi
And because our error handling code rarely, if ever gets executed(I often see
trivial mistakes that lead to crashes in error handling case), I think the
erlang philosophy of “let it crash” is the best approach IMHO.

~~~
rumcajz
I like to pepper my code with asserts for that reason. But you can also get
the case where the process crashes and get restarted in an infinite loop, so
it's not a panacea either.

~~~
macintux
That’s where Erlang’s supervision model comes to the rescue. Restart until it
becomes obvious something more serious is wrong and then raise a flag.

The nice thing about Erlang is that practically every line of code is an
assertion and they’re all live in production. Such a huge advantage over
development assertions that get thrown away for prod.

~~~
MrBuddyCasino
I think Rust got this right, too. No Exceptions, but a Result<R,E> enum return
type and the compiler forces you to handle the error case as well by doing
exhaustiveness checks.

~~~
macintux
Doesn't that lead to a lot of error handling code?

Another nice feature of the Erlang model is that, often, you can code the
happy path and forget the error checking. Makes for much
tighter/cleaner/easier-to-read code.

~~~
jjnoakes
In many cases the question mark operator can propagate errors for you. It is
both explicit and non-verbose.

------
hnmonkey
This might be a shot in the dark but does anyone know how to test error
handling in Rails? Say for example you have a method that has a begin block
with 10 lines of code in it and a rescue that does some logging or does a puts
statement. How do you write a spec that tests the code within the rescue
automatically without modifying the 10 lines of code? My google-fu on this one
has failed.

~~~
Perseids
I'm probably misunderstanding your situation, but why don't you (from the test
suite) mock some function used in the begin block to throw an error and then
`expect(the_logging_function).to have_received` the logging output?

~~~
hnmonkey
That's not actually a bad idea at all. What however if you're just doing
simple variables manipulations in those 10 lines and not calling methods (I
realize that you can override the math methods as well)? Is there a way to
handle that easily or inject an exception into the begin block?

------
nyc111
Reading the article made me think that this is how medical doctors work. All
diseases are codified and symptoms are errors. Doctors try to match the error
to the error code and take action by prescribing codified medications.

~~~
anticensor
This is how diagnosers work, which is an important part but not entirety of
medical doctors. Research medicine is an extant thing.

------
teddyh
With exceptions being classes, a library designer can vastly simplify it for
the users by creating useful class hierarchies for the exceptions, allowing
the user to be as specific or generic as they wish or need.

~~~
hliyan
I've been writing code for 15 years (in Java, C++ and more recently JavaScript
and Go). I have finally given up on exceptions and settled on return values (I
particularly like Go's system). Exceptions, while theoretically superior,
simply tempts even good programmers to just kick errors down the callstack. I
prefer guard clauses [1].

[1] [http://wiki.c2.com/?GuardClause](http://wiki.c2.com/?GuardClause)

~~~
dtech
I like the functional approach with Try, basically the exception becomes part
of the return type, and the code chooses to either return an exception or the
actual value.

    
    
       fun doSomething(arg: X): Try<Y>
    

Since the return signature needs adjusting, this leads to developers very
consciously making the choice to either handle the error in the function,
therefore avoiding adjusting the return type, or let the caller deal with it
if it isn't logical to handle the error there.

~~~
wool_gather
Is this the same as `Result<Y>` (where `Result` is a sum type that contains
either a value of `Y` or an "Error")? I haven't see it called `Try` before.

~~~
dtech
It's a common name for the concept in Java, Scala, and to a lesser extend
Javascript land. But it's just a name for a `T | Exception` type.

