We had an internal discussion recently on checked versus unchecked exceptions somewhat mirroring Howard Lewis Ship’s recent commentary on the subject. The discussion was about a library that throws a RuntimeException in cases where the client programmers really should be handling the situation – there is some predictable error case and the client can and should handle it. There was heated argument that this should therefore be a checked exception – I am not a fan of checked exceptions and suggested alternatives.
Now, Java is almost the only language that actually supports checked exceptions. Even in Java they are many who consider them A Bad Idea™ that should be avoided. Why? Because they are still exceptions – designed for exceptional circumstances – and using them for control flow just isn’t good programming. Checked exceptions force the client code to deal with the exceptional case using clumsy constructs and are generally so un-user-friendly that the most common way of handling them is to catch then call ex.printStacktrace() which is actually the worst possible thing to do! The ever increasing use of alternate languages on the JVM means that exceptions are absent in any other language that needs to interop with your Java API anyway.
There are further ways in which exceptions are just not ergonomic. Exceptions do compose reasonably well, but exception ergonomics are not just about the compositional semantics. There is no possibility of contravariance in an exception signature for instance (so you have to either have throws Exception (eg. Callable.call()) which is meaningless and defeats the whole point – or you have parametric exceptions (which I have never seen actually used). While you can have covariant exception signatures they aren’t very useful in practice either. One of the major reasons the closure proposal has been delayed so much is the complexities thrown up by checked exceptions. Lastly, an API with a checked exception in it is stunningly hard to modify and maintain source backwards-compatibility. Your only chance is if you have parameterised your exception type (again, see closure proposal discussions for how well that works).
So given how bad they are, what are our alternatives?
For instance if you have the following signature:
/** Get the numeric value contained in the parameter **/
Integer parse(String) throws NumberFormatException
How do we make this handle cases where the input isn’t made solely of digits without throwing exceptions? We could return null? bzzzzt! Back of the class. Nulls are very, very bad, and encoding null with any sort of meaning (in this case that the input was unparsable) is terrible. Nulls are attractive in that null is always a member of any reference type, but they lead to all sorts of problems – not the least being the dreaded NullPointerException. So, no nulls and no exceptions, where do we go next?
A good first effort is to use an Option or Maybe type, where the possible results are Some number or None. This is how it looks now:
/** Get Some numeric value from the parameter, or None */
This is quite a lot better than null as the result is definitely not null and can have lots of useful functions on it for dealing with the result if present, but it doesn’t really deal with the failure case very well. Ie. what happened if I get a None? Why did it not parse?
In this case what we want is something like an Either (technically a disjoint union). This fairly straight-forward type represents either an X or an A. By convention the X (left) is for problems/exceptions and the A (right) is for the expected result as it is the right side.
* Get either a numeric value from the parameter,
* or a NumberFormatValidation explaining what was wrong
Either<NumberFormatValidation, Integer> parse(String);
Now we have a more honest result type. It tells you that it will definitely for each and every String return you something, either the Integer value or a NumberFormatValidation (or an ErrorMessage, or an error String or whatever). There is no null to deal with anywhere and no ugly catch blocks.
Furthermore, as the actual type if the lhs is not restricted to subclasses of Exception, the opportunity to use it for carrying data is much broader. Subclassing Exception to allow carrying of data (such as ValidationException or some-such) has been clumsy at best in practice. With Either we can use any structure to represent failure at all.
Java doesn’t make using these things syntactically much nicer than using checked exceptions (that is a general problem with Java) but there are enough benefits to seriously consider these techniques as significant alternatives even in a pure Java project.
Whether or not you use these features directly, you are likely to come across more and more code that uses them as the state of the art moves on.
: There are of course specialised [ahem] exceptions to this rule but we’ll ignore that for now.