Checked exceptions I love you, but you have to go(googletesting.blogspot.com)
googletesting.blogspot.com
Checked exceptions I love you, but you have to go
http://googletesting.blogspot.com/2009/09/checked-exceptions-i-love-you-but-you.html
11 comments
I often wrap checked exceptions in lower level code in higher level checked exceptions.
So if I was reading a configuration file I would throw something like ConfigurationReadException and then wrap the IOException in this. Then if I switch to reading the configuration file from a database (for example), I can wrap an SQLException instead of the IOException. Encapsulation isn't broken and I still have checked exceptions.
The checked exception didn't break encapsulation here, you did. You have alot more than two options, and the two you listed aren't very good ones.
If foo calls bar, something might go wrong, and foo should take some kind of action. If bar can't complete because of an IOException, it should throw it (or wrap it and throw the wrapper). Foo now knows that bar may not complete successfully, and can do what makes sense there (throw it's own exception, retry, etc). If foo can't complete because bar didn't complete, it should tell it's caller. If it matters that the reason Foo couldn't complete was an IOException, throw it, otherwise throw a more meaningful exception.
If foo calls bar, something might go wrong, and foo should take some kind of action. If bar can't complete because of an IOException, it should throw it (or wrap it and throw the wrapper). Foo now knows that bar may not complete successfully, and can do what makes sense there (throw it's own exception, retry, etc). If foo can't complete because bar didn't complete, it should tell it's caller. If it matters that the reason Foo couldn't complete was an IOException, throw it, otherwise throw a more meaningful exception.
Let me try to phrase the argument more clearly: if you want to pass on a checked exception via redeclaration in the "throws" clause, you've broken encapsulation because a change to the lower layer implies changes all the way up the stack. If bar() was originally calling readDatabase() that threw a SQLException, and I change it to call readFile() that throws an IOException, if I'm passing that exception up the stack I have to change bar's signature purely due to an implementation change, and so on up the stack. So a change at the lower layer has now forced me to make changes all the way through my application stack. Henceforth, encapsulation is broken by what should just be an implementation change.
The only way to avoid that break of encapsulation is to catch the exception immediately in bar(), and then do something with it: either handle it, or rethrow it as something else, either a runtime exception or as a different checked exception. But that also isn't really ideal; in my experience, about 98% of the time even checked exceptions are throw-up-your-hands sort of programmer errors that you want to bounce a fair way up the stack to some more central error-handling location, or to kill the high-level operation being attempted. In other words . . . you generally just want to treat them exactly like runtime exceptions.
So while the theory of type-safety around checked exceptions is nice and all, in practice if you actually use them as part of type signatures they massively couple all the layers of your application, and I find them much more annoying than useful.
The only way to avoid that break of encapsulation is to catch the exception immediately in bar(), and then do something with it: either handle it, or rethrow it as something else, either a runtime exception or as a different checked exception. But that also isn't really ideal; in my experience, about 98% of the time even checked exceptions are throw-up-your-hands sort of programmer errors that you want to bounce a fair way up the stack to some more central error-handling location, or to kill the high-level operation being attempted. In other words . . . you generally just want to treat them exactly like runtime exceptions.
So while the theory of type-safety around checked exceptions is nice and all, in practice if you actually use them as part of type signatures they massively couple all the layers of your application, and I find them much more annoying than useful.
Couldn't we just have the compiler automatically figure out this propagation, and then toss a warning if one could ever reach the top level of a thread? I mean, it seems like a much easier problem than ML-style type inference.
catch (IOException ioe) { throw new MyObjectXYZContractException ("Can't do XYZ", ioe); }
Fundamentally I want to be able to ask my programming language "what exceptions might propagate to here?" Having this ability allows me to make stronger claims that my program is correct, because then I can say that the only exceptions which can propagate to that point are of a type I have decided not to handle (like the filesystem being disconnected) and this can be a strong safety property.
I think Java has ruined checked exceptions for the rest of us, because this article makes good points and successfully knocks down Java's exception system. I don't think this invalidates the concept of checking one's exceptions, though!
What would I do instead? Since it's always the prerogative of the receiver of a possible exception to rethrow it uncheckedly, then why not allow the type system to promote (/demote) a checked exception to unchecked at the catchpoint, without requiring an actual catch block? Some system like that could reduce the syntax clutter which is the root of the author's beef.
I think Java has ruined checked exceptions for the rest of us, because this article makes good points and successfully knocks down Java's exception system. I don't think this invalidates the concept of checking one's exceptions, though!
What would I do instead? Since it's always the prerogative of the receiver of a possible exception to rethrow it uncheckedly, then why not allow the type system to promote (/demote) a checked exception to unchecked at the catchpoint, without requiring an actual catch block? Some system like that could reduce the syntax clutter which is the root of the author's beef.
Clojure catches all exceptions from Java methods, rethrowing them as runtime exceptions. In other words, it gets rid of checked exceptions. (It has to, if you think about it.)
No type system support... but no catch block, either.
No type system support... but no catch block, either.
It's possible to analyze programs to find out what exceptions can be delivered to a particular point in the program. As an example, here is such an analyzer for OCaml code:
http://caml.inria.fr/pub/old_caml_site/ocamlexc/ocamlexc.htm
"Ocamlexc is a framework designed to statically analyze Objective Caml programs in order to discover potentially raised and uncaught exceptions. Such exceptions are harmful because they cause the immediate end of the running program. This tool can hence be seen as a static debugging tools, adding more safety to the already powerful static and strong typing system that makes ML programs so robust."
http://caml.inria.fr/pub/old_caml_site/ocamlexc/ocamlexc.htm
"Ocamlexc is a framework designed to statically analyze Objective Caml programs in order to discover potentially raised and uncaught exceptions. Such exceptions are harmful because they cause the immediate end of the running program. This tool can hence be seen as a static debugging tools, adding more safety to the already powerful static and strong typing system that makes ML programs so robust."
You could use RuntimeException as a superclass for your exceptions, or wrap checked exceptions in a RuntimeException. I'll often rethrow exceptions this way when I have to deal with something horribly wrong (like I/O or UI meltdown, etc.) in a place where I have to deal with the checked exception.. This is handled gracefully in the Eclipse workbench as a popup though, YMMV.
I would only recommend this as a last ditch effort to appease the Java compiler though, not an acceptable way to handle a common failure mode.
As a side note, does anyone else find the pattern (well, I would say headache) of dealing with checked exceptions similar to working with the IO monad in Haskell?
I would only recommend this as a last ditch effort to appease the Java compiler though, not an acceptable way to handle a common failure mode.
As a side note, does anyone else find the pattern (well, I would say headache) of dealing with checked exceptions similar to working with the IO monad in Haskell?
I often explain monads to laypeople as "It's a way to abstract away extra information that's passed through the computation, and define arbitrary ways to handle that information." One of the possible types of "extra information" that may be attached are the types of exceptions thrown: see the Error monad.
And yeah, monads have most of the same problems that checked exceptions do. Each action in the monad must have the same type; you can't just ignore one of those bits of extra information and declare that it doesn't matter in this context. For example, if your complicated algorithm writes to a log file, you probably don't care in the rest of the program and could just as easily treat it as pure. But the type system won't let you, unless you fall back on cheats like unsafePerformIO.
I dunno really what the right answer for this is, but I really like the Erlang philosophy toward errors. "Let it crash, then do something simpler." Instead of trying to enumerate all the ways your program can fail, it just assumes that you will fail sometimes, so you ought to try multiple ways to succeed instead. That requires quite a paradigm shift from the way programmers normally think about programming, though.
And yeah, monads have most of the same problems that checked exceptions do. Each action in the monad must have the same type; you can't just ignore one of those bits of extra information and declare that it doesn't matter in this context. For example, if your complicated algorithm writes to a log file, you probably don't care in the rest of the program and could just as easily treat it as pure. But the type system won't let you, unless you fall back on cheats like unsafePerformIO.
I dunno really what the right answer for this is, but I really like the Erlang philosophy toward errors. "Let it crash, then do something simpler." Instead of trying to enumerate all the ways your program can fail, it just assumes that you will fail sometimes, so you ought to try multiple ways to succeed instead. That requires quite a paradigm shift from the way programmers normally think about programming, though.
The difference between error codes and exceptions are that, for error codes, you must check the result of every function call and method and in some way handle that result. Contrast this to exceptions, where the advantage is that your error handling is centralized. You should throw as much as necessary and catch in a few key places.
Checked exceptions ruins that advantage of exceptions -- you're back to writing a whole bunch of buggy error handling code rather than just a few key catches. The result is errors are being hidden, masked, or ignored for absolutely no gain except to satisfy the compiler.
Checked exceptions ruins that advantage of exceptions -- you're back to writing a whole bunch of buggy error handling code rather than just a few key catches. The result is errors are being hidden, masked, or ignored for absolutely no gain except to satisfy the compiler.
Relying on the IDE to know which exceptions can be thrown is nicer I think.
The .NET pattern of inner exceptions is nice too (relevant to the article's example, you could throw an NHibernateException(inner exception:SQLForeignKeyViolatedException)).
It's nice I guess from a certainty and stability standpoint in Java to know that you won't have an exception crash your system, but even then you aren't going to be sure because some exceptions do crash your system because they aren't checked (e.g. null pointer, array index etc.).
In my opinion Java is a dead language anyway. The language's bloat (exceesive boiler-plate required for everything), lack of high-level functionality and general approach to boxing in the programmer mean that new programmers don't want to use it (most will learn it though).
The slow initialisation time (JVM problem) and lack of native interfaces (despite the work done here) mean that companies don't want to use it for customer products.
For internal apps, I'd wager that the company just wants something that most people know and that is quick to develop in, and while Java has some ground here I think other languages will be making strong headway too, especially when you have some control on the deployment platforms.
Java just strikes me as dissapointing now. It is probably single-handedly responsible for us getting over the fear of automatic memory management and GC. Similarly it's had massive influences on virtual machines and easy cross-platform deployment. Just about every CS student in the world knows Java. But right now it has failed to innovate with the resurgent popularity for language functionality, address the issue of bloat or develop on the issues that discourage people actually using it for cross-platform deployment.
In the mobile sector it's pretty strong, but Objective-C/iPhone will probably hurt that, and it's only strong because of the JVM's portability. On the web it had the potential to take out flash, but the incredible load time and associated lag of the JVM initialising has killed that chance.
Overall, Java is only afloat because it is too big to sink.
The .NET pattern of inner exceptions is nice too (relevant to the article's example, you could throw an NHibernateException(inner exception:SQLForeignKeyViolatedException)).
It's nice I guess from a certainty and stability standpoint in Java to know that you won't have an exception crash your system, but even then you aren't going to be sure because some exceptions do crash your system because they aren't checked (e.g. null pointer, array index etc.).
In my opinion Java is a dead language anyway. The language's bloat (exceesive boiler-plate required for everything), lack of high-level functionality and general approach to boxing in the programmer mean that new programmers don't want to use it (most will learn it though).
The slow initialisation time (JVM problem) and lack of native interfaces (despite the work done here) mean that companies don't want to use it for customer products.
For internal apps, I'd wager that the company just wants something that most people know and that is quick to develop in, and while Java has some ground here I think other languages will be making strong headway too, especially when you have some control on the deployment platforms.
Java just strikes me as dissapointing now. It is probably single-handedly responsible for us getting over the fear of automatic memory management and GC. Similarly it's had massive influences on virtual machines and easy cross-platform deployment. Just about every CS student in the world knows Java. But right now it has failed to innovate with the resurgent popularity for language functionality, address the issue of bloat or develop on the issues that discourage people actually using it for cross-platform deployment.
In the mobile sector it's pretty strong, but Objective-C/iPhone will probably hurt that, and it's only strong because of the JVM's portability. On the web it had the potential to take out flash, but the incredible load time and associated lag of the JVM initialising has killed that chance.
Overall, Java is only afloat because it is too big to sink.
Java the language will soon be replacing COBOL as the boring legacy language of choice.
Java the virtual machine is alive and well and I suspect will have a long life.
Java the virtual machine is alive and well and I suspect will have a long life.
Relying on the IDE to know which exceptions can be thrown is nicer I think.
No. If you can't remember what it throws, you can't remember the documentation. If you can't remember the documentation, you don't fully know how to use it properly. Go read the documentation; IDEs are a crutch.
EDIT: I'm completely serious. http://docs.python.org/library/ and help() are my best friends, in that order.
No. If you can't remember what it throws, you can't remember the documentation. If you can't remember the documentation, you don't fully know how to use it properly. Go read the documentation; IDEs are a crutch.
EDIT: I'm completely serious. http://docs.python.org/library/ and help() are my best friends, in that order.
I think knowing every exception a method can throw and knowing the method are different things. I want to be able to write code, while I'm in the zone and thinking about the business problem, not have to jump and navigate a web site for every method call.
And I didn't say you can't check the domentation from the IDE (through a nice context link) but I still think mouseover on a method that lists exceptions (as Visual Studio does) is the ideal solution as it gives me the information I need as I code.
NB: VS exception info is comment documentation based, I have no idea why the compiler doesn't generate this information as much as possible becuase then you are getting some of the advantage of checked exceptions without the cost.
As a counterpoint to knowing the documentation, consider the System.Reflection.MethodBase.Invoke method in .NET which I have in front of me, are you going to remember all these exceptions:
And I didn't say you can't check the domentation from the IDE (through a nice context link) but I still think mouseover on a method that lists exceptions (as Visual Studio does) is the ideal solution as it gives me the information I need as I code.
NB: VS exception info is comment documentation based, I have no idea why the compiler doesn't generate this information as much as possible becuase then you are getting some of the advantage of checked exceptions without the cost.
As a counterpoint to knowing the documentation, consider the System.Reflection.MethodBase.Invoke method in .NET which I have in front of me, are you going to remember all these exceptions:
// Exceptions:
// System.Reflection.TargetException:
// The obj parameter is null and the method is not static. -or- The method
// is not declared or inherited by the class of obj. -or- A static constructor
// is invoked, and obj is neither null nor an instance of the class that declared
// the constructor.
//
// System.ArgumentException:
// The elements of the parameters array do not match the signature of the method
// or constructor reflected by this instance.
//
// System.Reflection.TargetInvocationException:
// The invoked method or constructor throws an exception.
//
// System.Reflection.TargetParameterCountException:
// The parameters array does not have the correct number of arguments.
//
// System.MethodAccessException:
// The caller does not have permission to execute the constructor.
//
// System.InvalidOperationException:
// The type that declares the method is an open generic type. That is, the System.Type.ContainsGenericParameters
// property returns true for the declaring type.I didn't say you have to know the documentation, and I'll admit I'm behind on how much of it IDEs show you. In my recollection, you generally get a list of language-level constructs and that's it. For example, the names of those exceptions are not as useful as the text you've shown there. If VS does that for exceptions, great! Contrast it with the 2003/2005 IntelliSense feature, where you just get method prototypes.
I misinterpreted "No. If you can't remember what it throws, you can't remember the documentation" as know the documentation but I can see your point now.
Yeah and you just get a list of possible exceptions rather than a description of each which is a fair point to make too. Maybe it's configurable though.
Yeah and you just get a list of possible exceptions rather than a description of each which is a fair point to make too. Maybe it's configurable though.
FileNotFoundException is not really a corner case, it's all but unavoidable race. No matter how soon you open the file after verifying its existence, it could have gone away. And the only reasonable thing you can do in the face of it is route the message that the file is missing to the user one way or another, and possibly exit.
In that way, it makes sense for a top-level handler, such as at the main() routine or in the event loop in a GUI app, to be the one to handle it, and for all intermediates to pass it up. The fact that most meaningful applications do I/O at some level, means that almost the only routines that can be indicated not to throw this exception or a similar I/O exception, are functionally pure ones, or the ones that implement the aforementioned user notification.
In that way, it makes sense for a top-level handler, such as at the main() routine or in the event loop in a GUI app, to be the one to handle it, and for all intermediates to pass it up. The fact that most meaningful applications do I/O at some level, means that almost the only routines that can be indicated not to throw this exception or a similar I/O exception, are functionally pure ones, or the ones that implement the aforementioned user notification.
Why would you verify it's existence? Just open it, you either get it or you don't. If you don't then catch the exception and do what you would have done if you'd have checked and it wasn't there. Honest question?
Opening a file (or attempting to) is resource intensive and throwing/catching exceptions is sloooow.
Both opening the file and checking whether it exists involve a syscall, which dwarfs the overhead of throwing and catching an exception. If you check whether it exists and then try to open it, that's two syscalls.
On reflection, it occurs to me that a Sufficiently Smart Compiler (TM) could figure out that you were going to open the file right after checking whether it exists and just call open(2) as the existence check instead of stat(2) and then open(2). This seems to depend on the details of the library implementation as well, though.
I don't know how many files you open in your applications, but in all of mine, the number is so small that I could not care less about the overhead of an exception.
> and throwing/catching exceptions is sloooow.
That's not seeing the forest for the trees. Unless a specific inner loop of the program shows up in a profiler as the hotspot because of an exception, this just isn't something you should ever worry about in 99% of code. Don't throw out the nice abstractions that make code simple unless you know for sure it's the actual problem, rarely is that the case for throwing exceptions. In this particular instance, I'd say you're just wrong about not using an exception.
That's not seeing the forest for the trees. Unless a specific inner loop of the program shows up in a profiler as the hotspot because of an exception, this just isn't something you should ever worry about in 99% of code. Don't throw out the nice abstractions that make code simple unless you know for sure it's the actual problem, rarely is that the case for throwing exceptions. In this particular instance, I'd say you're just wrong about not using an exception.
The slowness of exceptions depends on your language / implementation of you language.
Why does everyone seem to think this a black-and-white issue? Checked exceptions are appropriate for some things, unchecked exceptions are appropriate for others. Use the right tool for the job.
Some parts of Java's API have made poor decisions in their use of checked exceptions (the close methods on file input streams and database connections, for example), but that doesn't invalidate the concept.
Correct use of checked exceptions helps create more solid code with less testing required, because the compiler is able to do checks that would otherwise have to be written into unit tests. Incorrect use of checked exceptions is what leads to all of the objections I've seen listed here.
Some parts of Java's API have made poor decisions in their use of checked exceptions (the close methods on file input streams and database connections, for example), but that doesn't invalidate the concept.
Correct use of checked exceptions helps create more solid code with less testing required, because the compiler is able to do checks that would otherwise have to be written into unit tests. Incorrect use of checked exceptions is what leads to all of the objections I've seen listed here.
Why does everyone seem to think this a black-and-white issue?
You answered your own question. Inappropriate use of checked exceptions is built into the heart of the Java standard libraries, which means that (a) every new Java programmer is taught, consciously or subconsciously, to use them everywhere, at least until the programmer is very explicitly educated about their badness; (b) even experienced Java programmers must deal with them every day, and 95% of the checked exceptions they deal with are an unredeemed pain in the ass, so before long the very sight of one makes you flinch. It's Pavlovian.
In a language community where checked exceptions were a completely optional add-on which was only used when appropriate, they might have a much better reputation.
You answered your own question. Inappropriate use of checked exceptions is built into the heart of the Java standard libraries, which means that (a) every new Java programmer is taught, consciously or subconsciously, to use them everywhere, at least until the programmer is very explicitly educated about their badness; (b) even experienced Java programmers must deal with them every day, and 95% of the checked exceptions they deal with are an unredeemed pain in the ass, so before long the very sight of one makes you flinch. It's Pavlovian.
In a language community where checked exceptions were a completely optional add-on which was only used when appropriate, they might have a much better reputation.
Checked exceptions are probably are of my biggest pet-peeves about Java (erasure and lack of closures/first order functions are the other ones). They create excess verbosity, but what good are they if most code I've seen does this:
try { foo.bar(); } catch (FooException fe) { logger.error(fe); }
A descriptive crash would be more useful, especially if the exception is thrown as a result of an invariant being violated. Scala is a step forwards in this manner, but now there's the issue of remembering which Java methods (foreign to Scala) rely on exceptions to convey important information (e.g. anything which throws InterruptedException).
try { foo.bar(); } catch (FooException fe) { logger.error(fe); }
A descriptive crash would be more useful, especially if the exception is thrown as a result of an invariant being violated. Scala is a step forwards in this manner, but now there's the issue of remembering which Java methods (foreign to Scala) rely on exceptions to convey important information (e.g. anything which throws InterruptedException).
Most try-catches are considered boiler plate and are often generated. For example of the default options in NetBeans generates the following:
try { foo.bar(); } catch (Exception ex) { Logger.getLogger(InvalidConversionException.class.getName()).log(Level.SEVERE, null, ex); }
try { foo.bar(); } catch (Exception ex) { Logger.getLogger(InvalidConversionException.class.getName()).log(Level.SEVERE, null, ex); }
FWIW, Scala lets you call java code that would throw checked exceptions - and not check them. The checking is only enforced at compile time by javac.
The most important problem with checked exceptions in Java is the way they interact with inheritance. If you could just declare "throws" everywhere, you could at least live up to the ideal of knowing the exceptions to expect. But you can't override and add a new "throws". Substitution principle, yeah, I know. But why did they design Runnable without "throws Exception"?
Runnables don't throw checked exceptions for the same reason that main() doesn't - because there's nothing sensible that could catch it.
You can declare main() as "throws Exception".
I typically use 3 categories of exceptions when building frameworks or libraries.
Two are unchecked RuntimeException extensions to indicate operating environment issues, and, implementation issues (aka bugs).
Finally, if the api semantics seem to indicate it, api level checked exceptions indicating misuse of the api (aka user errors). Typical example are cases where the api semantics require return values which preclude uniform application of an 'error code' as a viable means of alerting user error. I don't believe it is a good practice to throw an unchecked exception in these cases.
Both checked and unchecked exceptions have their uses.
Two are unchecked RuntimeException extensions to indicate operating environment issues, and, implementation issues (aka bugs).
Finally, if the api semantics seem to indicate it, api level checked exceptions indicating misuse of the api (aka user errors). Typical example are cases where the api semantics require return values which preclude uniform application of an 'error code' as a viable means of alerting user error. I don't believe it is a good practice to throw an unchecked exception in these cases.
Both checked and unchecked exceptions have their uses.
I think it's really just a fundamental problem with the whole idea, and it's why any attempt to "fix" them will fail: the idea of checked exceptions is just deeply flawed.