Enforced Error Handling
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.<p>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.<p>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.<p>Another thing we know is that if programmers <i>are</i> 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.<p>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.<p>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.<p>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.<p>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.<p>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, <i>can</i> 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.