I know Mr. Harold is much more experienced than me. It's pretty obvious he's more of an authority on most programming subjects. With that caveat I give my reasons why I despise checked exceptions in Java.
I've never seen code that got cleaner because of the use of checked exceptions. If you have a fairly complex piece of functionality that you've refactored mercilessly, it is quite possible to end up with a deep object hierarchy. If the checked exception is thrown at the bottom, that pollutes every interface on the way up the call chain to the method where the exception is handled. Also, whenever you are ready to catch the exception, you have to have a try/catch block and that aesthetically looks like an if-else statement, which means it is really easy to write it in an ugly way (merely having 3 lines of code for each the try and catch part is enough). All of this inevitably slows me down as I have to come up with a way to deal with those problems. It also frequently tempts me to just eat the checked exception rather than handle it.
I also think throwing a checked exception assumes too much about how I want to use that particular class/function. Suppose I'm using some sort of parsing tool to test complicated output (say, HTML). I'm only using this in tests. I do not want to have to handle exceptions. Even if it was in production code I may not actually care about addressing anything but the happy path.
I cannot effectively argue I'm not a newbie, writing buggy, shaky software. But I can't help but feel that the concerns of most programmers are more aligned with mine.
His argument that a library that accepts an interface without declaring all possible exceptions a method of that interface might throw is badly designed is misguided. This assumes that the writer of the library can know all possible custom exceptions that code written by the user might throw, which is impossible. But it is perfectly valid if the code that calls the library passes in an instance implementing the interface that might throw a CantFrobnicateException that the library doesn't know anything about if the calling code knows what to do if the library call fails with that exception.
The real design error here are still checked exceptions. What you really want are dynamic exceptions with an optional whole program analyzer that detects possible code paths that could lead to uncaught exceptions.
Checked exceptions are problematic because they version poorly, they implicitly presume the wrong model of exception handling, and more importantly, they prematurely decide the application's desired policy for error handling by encoding the "checkedness" in the type itself, where it is not available for configuration by the programmer.
The poor versioning is obvious. The OP says "The superclass/interface was not designed properly for extension. Specifically it did not take into account the exceptions overriders/implementers might reasonably want to throw" - and the only problem with that is the well-known difficulty in predicting the future.
But actually, it's more subtle: exceptions are usually a desired encapsulation violation. When programming to an abstract interface, you don't want to know the details of how it's implemented, but all the ways it can fail are a function of how it's implemented. You can't reasonably require the design of the most abstract levels of the system to predict every possible low-level implementation failure and state them out long-hand; the only reasonable implementation would be "throws Exception", which defeats the whole point of checked exceptions.
By presuming the wrong model of handling, I'm referring to the burden of creating a tunnel through the type system for your exception type between the point of the throw and the point of the catch. This burden is set up to optimize for catching sooner rather than later. But the thing is, usually you never want to catch; usually, the only exception catching you want done is at the highest levels of the system, in an event loop or request dispatcher. If you were expecting an exception you'd want to catch, the situation isn't exceptional; instead, you shouldn't be using an API that throws. .NET's pattern of e.g. Int32.Parse() vs Int32.TryParse() exemplifies this.
The OP tries to argue against this with the distinction between runtime errors and exceptions: "checked exceptions do not require the programmer to provide handlers in situations where no meaningful action can be taken. When no meaningful action can be taken Java programs throw Errors". And this brings me to my third point. The determination for whether meaningful action can be taken varies from program to program, and is encoded in the very exception handlers themselves - i.e. it's the programmer who makes that choice, not the people who defined the relevant exception types.
His examples of error - out of memory error, stack overflow, class not found, etc. - actually make more sense as reasons to completely terminate the application, rather than make any attempt to catch. They're not really errors so much as panics.
If you application is a server then out of memory error during a request means that the request failed but not that you should shut down the server.
class not found means that some functionality is unavailable but you could provide a workaround that might have some disadvantages compared to the absent version but would work. For example, in Python (ImportError plays role of class not found here)
import cElementTree as ElementTree # fast C extension
from elementtree import ElementTree # ~30 time slower
If the code is in a library it is not your call to decide what is satisfactory performance and what requirements are. A user of your library could decide that running this code inside JVM (via Jython) is more important than using C extensions for speed.
# If a real XML parser is available, feedparser will attempt to use it. feedparser has
# been tested with the built-in SAX parser, PyXML, and libxml2. On platforms where the
# Python distribution does not come with an XML parser (such as Mac OS X 10.2 and some
# versions of FreeBSD), feedparser will quietly fall back on regex-based parsing.
xml.sax.make_parser(PREFERRED_XML_PARSERS) # test for valid parsers
from xml.sax.saxutils import escape as _xmlescape
_XML_AVAILABLE = 1
_XML_AVAILABLE = 0
data = data.replace('&', '&')
data = data.replace('>', '>')
data = data.replace('<', '<')
for char, entity in entities:
data = data.replace(char, entity)
If you were expecting an exception you'd want to catch,
the situation isn't exceptional
Here your argument starts applying to all exceptions, not just checked exceptions. Consequently, the argument is obviously a hasty generalization, as breaking away from the regular flow of the code, by signaling an exceptional situation with a construction designed for that purpose, is a regular part of almost any programming language.
When is a situation exceptional? When it happens once a minute, once a day, once a year? That a situation isn't 'exceptional', according to you, doesn't mean you shouldn't want to use a special construction to signal that it is a special result. Firstly, it removes the burden to explicitly check the return value of every function call. If it returns, the function did its job. It increases the readability of code. Secondly, if you check the return value anyway, as often happens, 'special' cases of the regular return value, like 'null' or a special integer or string, always introduce problems. Problems of ambiguity, bookkeeping/documentation and unfortunate choices of the 'special' values are the most common ones. Exceptions a good solution to the problem it solves. Their downside is that they are easily abused.