Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Enforced Error Handling (250bpm.com)
18 points by luu on Dec 21, 2015 | hide | past | favorite | 9 comments


"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.


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?


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.


The problem with checked exceptions a-la Java is that the type system is not polymorphic. You need to list the exceptions you raise explicitly and there is no way to say "I raise the same exceptions that this object I receive can raise"


Basically. You need to handle all checked exceptions (Exception class), but not unchecked exceptions (RuntimeException class).


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.


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.


> 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.


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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: