
Enforced Error Handling - luu
http://250bpm.com/blog:68
======
greenyoda
_" I wonder whether there is an imperative language that enforces explicit
handling of errors..."_

Java does that. If a method can throw an exception, you need to either handle
the exception in a 'catch' block or throw it up the call stack for another
method to handle. If you do neither, your code won't compile.

~~~
placeybordeaux
In the second paragraph the author seems to be only talking about errors as C
and go have then, specifically as errors as values, not as exceptions. The
important difference being that exceptions can occur anywhere and can lead to
non explicit bailing out of a function.

Exceptions complect failure and control flow.

Judging from the docs (and the comments on the blog) it looks like rust might
have largely what the author is looking for. Can anyone speak to that?

~~~
snuxoll
Rust handles errors in a functional-style way with std::result<T, E>. This is
the same technique I use in F# code with a identically named discriminated
union.

------
jallmann
Compiler-enforced discipline is the best kind of discipline. What the author
wants is ML. Specifically, he wants:

    
    
        1. A statically typed language
        2. Exhaustive handling on the type of returned values
        3. To discourage impure functions or mutable state
    

Other languages (eg Rust, Haskell) fit these criteria as well. C might be
forgiven given its age, but Go really has no excuse for such a weak type
system.

------
Chris_Newton
I’m not convinced by the fundamental premises the article opens with, i.e.,
that exceptions are only useful if you can tolerate a program crashing now and
then, and that if you care about reliability you must use C-style error
handling instead, checking everything at every level of the stack.

To my knowledge, there is no known system in mainstream use that reliably
enforces that check-at-every-stage policy. Moreover, it is not immediately
obvious how one would create such a system in a practically useful way, at
least not without immediately running into the same sorts of issues commonly
used to criticise exception handling in terms of keeping track of every
possible failure mode that lower level code could indicate.

One thing we do know is that if programmers are not absolutely compelled to
handle errors then sometimes they won’t. The article itself cites an example
from a very common library function widely called in C code among many other
languages.

Another thing we know is that if programmers _are_ compelled to handle errors
then they will cheat. Specifically, sometimes they will write useless code to
satisfy the technical requirements of the language while not in fact doing
anything useful to recover from the error. Java’s checked exception mechanism
is infamous for unintentionally promoting this sort of programming, and the
paper cited by the post we’re discussing specifically calls this out as a real
world problem.

A third thing we do know is that sometimes there isn’t anything useful you can
do in response to some failures. Maybe your program simply won’t be able to
complete its task successfully under those circumstances and the best you can
do is fail as gracefully as possible.

Given these constraints, systematic error handling at an architectural level
seems to be the more promising strategy, and there seems little reason to
prefer fine-grained, manual error checking code everywhere instead.

Exceptions are one way of implementing error handling at an architectural
level, and to me they have always seemed a natural fit for sound functional
decomposition in a software design. As a base case, you can always reduce to a
situation where an operation either completes successfully or it fails and you
don’t distinguish between different types of failure. This is just the
universal catch block or its equivalent in your language of choice. Anything
more specific than that can be added as necessary. The one thing you can’t do
is have higher level code accidentally continue as if there is nothing wrong
once lower level code has explicitly indicated that a failure has occurred.

Another architecture-level strategy is an Erlang idiom, where a failed process
is effectively allowed to crash completely, with a separate supervisor process
then detecting this and perhaps restarting the failed process to recover. This
is somewhat analogous to the base case with exceptions and universal catch, in
that normal execution can’t inadvertently continue once a failure resulting in
a crash happens.

It seems to me that either of these strategies, or any other systematic
mechanism that imposes detection and recovery at an architectural scale, is
likely to be more robust in the real world than a system that relies on
manually checking everything. The latter, in the event of any oversight, _can_
allow higher level code to continue to operate as if nothing is wrong even
after code at a lower level has explicitly determined that a failure has
occurred, presumably carrying around an error that may subsequently manifest
as a different kind of failure and ultimately a system fault. This seems to be
exactly what the article was trying to argue against, yet the proposed
strategy for dealing with errors, one manual level at a time, seems to be just
about the worst way we know to avoid it.

~~~
marcosdumay
> To my knowledge, there is no known system in mainstream use that reliably
> enforces that check-at-every-stage policy.

I got the impression the author is talking about Haskell. In pure code,
Haskell can behave exactly that way, and the checks don't even bother that
much, they are easy to deal with.

Problem is, not even Haskell is purely that way. IO operations just throw
exceptions just like in any other language.

~~~
Chris_Newton
It’s true that error handling in pure computations tends to be easier than the
general case.

A lot of the more tricky situations to deal with, IME, tend to be when you
have acquired some resource but then for some unexpected reason the resource
stops co-operating. Some of the most awkward problems then happen when you
can’t even clean up properly at that point, for example by deleting a
temporary file or committing a pending transaction on a database, because of
the failure, and so you wind up with some externally observable side effects
of your program that can’t be fixed.

Another awkward area is running out of system resources such as memory or
processes/threads, which many high level programming languages aren’t well
equipped to handle gracefully. Lazy languages like Haskell are relatively
vulnerable to thunks building up and consuming excessive resources, for
example, and while there are practical tools for mitigating that problem up to
a point, a lot of the neat theoretical models pretty much go out of the window
if you really do hit a hard limit in a production system.

That all said, carrying error information within a monadic framework would be
another good example of the kind of systematic error handling strategy I was
talking about before.

