Re: exceptions: checked or unchecked?
On Fri, 29 Aug 2008, marlow.andrew@googlemail.com wrote:
John B. Matthews wrote:
In article
<9dcee97f-2246-44b6-85a8-9365874a897a@73g2000hsx.googlegroups.com>,
marlow.andrew@googlemail.com wrote:
What is best practice for using exceptions in java? Checked or
unchecked?
But I maintain: several people consider checked exceptions to be an
experiment that failed.
Yes. These people are wrong.
See
http://corfield.org/blog/index.cfm/do/blog.entry/entry/Bruce_Eckel_on_Checked_Exceptions
and http://www.mindview.net/Etc/Discussions/CheckedExceptions for
example.
I suppose my question is really has this opinion that I stumbled upon
become std practise yet? If not, why not?
Because it's wrong. If you read Eckels' arguments, they're complete
nonsense. They boil down to "checked exceptions are bad because some
programmers write code which swallows them". That's not a problem with
checked exceptions, it's a problem with swallowing them - in other words,
it's a problem with programmers writing stupid code. If a method gets an
exception it can't handle, and which affects its ability to fulfil its
contract, then it should declare it, and let it pass through.
There is also an argument that thorough testing makes static checking of
exceptions unnecessary. There's a lot of truth in this - it's part of a
more general argument that testing makes any kind of static checking
unnecessary, and that argument is largely borne out by the success of
python, smalltalk, LISP, etc in building large and complex systems in the
real world. But that doesn't mean that strong typing is *bad*, or even
that it's unnecessary - it catches problems early, at their source,
without having to write a test to cover it. Tests can detect any kind of
problem - including a colossal number that static typing can't, but test
coverage will never by 100%; type safety coverage is always 100%.
There are other argument against checked exceptions. Here's why they're
not in C#:
http://discuss.develop.com/archives/wa.exe?A2=ind0011A&L=DOTNET&P=R32820
We're told there are two reasons. Firstly:
"E.g., A calls B, B calls C, C calls D, and D raises an exception that is
eventually handled by A. If C# required exception specifications, then
each of A, B, C, and D would have to contain exception-handling related
code even though only A and D do any actual work related to the
exception."
Apparently, writing "throws SomeException" is too hard for C# programmers.
I don't find this hard when writing java, and when i'm using Eclipse, it's
a matter of a few keystrokes.
Secondly:
"The number of possible exceptions. The number of exceptions is
unquestionably large. E.g., any code that adds two numbers could result in
an overflow exception, any code that divides two numbers could result in a
divide by zero exception, and any code that instantiates an object could
result in an out of memory exception."
Which is nothing more than absurd. I can only assume that this guy has
never actually looked at java - this is dealt with perfectly well with
RuntimeExceptions and Errors. And then:
"The lack of an increase in code quality is related to the response of
developers to the proliferation of exception specifications. Developers
who carefully examine all of the exception specification errors reported
by the compiler might see an increase in code quality, but this would come
at the expense of productivity. On the other hand, some developers will
respond to the errors by mindlessly adding whatever exception
specifications the compiler requires, and others will choose to subvert
the intent of the language designers by adding a generic exception
specification to every member."
We're back to the 'dumb programmer' argument. Oh, and the idea that
"developers who carefully examine all of the exception specification
errors reported by the compiler might see an increase in code quality, but
this would come at the expense of productivity" is a Microsoft classic -
increased code quality *is* increased productivity.
You can read any number of posts and articles bashing checked exceptions,
but ultimately, they all boil down to "bad programmers get it wrong", or
quite often "i don't know how to use exceptions properly". Those aren't
valid arguments against checked exceptions.
This post:
http://radio.weblogs.com/0122027/stories/2003/04/01/JavasCheckedExceptionsWereAMistake.html
Does raise an interesting problem, but wildly overextends by concluding
that checked exceptions are a bad idea. The problem is really about
higher-order programming: if you have a family of components, each of
which throws a different specific exception, and you want to write a
component that will use any one of them to provide a service to a client,
what do you do? Do you declare it to throw Exception? Swallow the
exceptions? Wrap the exceptions in RuntimeException? This is complicated
by the fact that the client might be in a position to deal with the
specific kind of an exception even if the intermediate component isn't.
That actually is a thorny problem. In classical java, i'd wrap and
re-throw with a checked exception, forcing the client to deal with the
possibility of failure, but letting them have access to the root cause. In
modern java, i'd consider using a generic throws clause.
To make that concrete, here's his example:
interface Factory<T> {
public T create() ;
}
class Pool<T> {
private Factory<T> factory ;
public T borrow() {
T obj = getObjectFromPoolIfPossibleOrNullIfNot() ;
if (obj == null) {
obj = factory.create() ;
addToPool(obj) ;
}
return obj ;
}
public void return(T obj) {
// return the object to the pool
}
}
Which is fine until you want to pool database connections:
class ConnectionFactory implements Factory<Connection> {
public Connection create() {
return DriverManager.getConnection(url, props) ; // oh snap!
}
}
That won't compile, because getConnection throws an SQLException.
The immediate problem is that neither the factory nor the pool have any
way to fail. They need to have that. The naive unchecked solution is
simple:
class ConnectionFactory implements Factory<Connection> {
public Connection create() {
try {
return DriverManager.getConnection(url, props) ;
} catch (SQLException e) {
throw new RuntimeException(e) ;
}
}
}
The more sophisticated unchecked solution involves a little more:
public class ConnectionException extends RuntimeException {
public ConnectionException(SQLException e) {
super(e) ;
}
}
class ConnectionFactory implements Factory<Connection> {
public Connection create() {
try {
return DriverManager.getConnection(url, props) ;
} catch (SQLException e) {
throw new ConnectionException(e) ;
}
}
}
The difference is in what the client code looks like. Say you have a
situation where you're using a connection pool, and you want to be able to
deal with failure. It looks like this:
Pool<Connection> pool ;
try {
Connection conn = pool.borrow() ;
} catch (RuntimeException e) {
// deal with it
}
The trouble is that the catch clause can catch all sorts of things that
aren't a failure to get a connection - NullPointerException,
ArrayIndexOutOfBoundsException, etc. The more sophisticated approach lets
you do this:
Pool<Connection> pool ;
try {
Connection conn = pool.borrow() ;
} catch (ConnectionException e) {
// deal with it
}
Which just catches what you're interested in.
Now, the huge problem with this is that both approaches also let you do
this:
Pool<Connection> pool ;
Connection conn = pool.borrow() ;
With no error handling at all. That's absolutely fine by the compiler!
What the Apache guys did was to go in the other direction, and declare
Exception:
interface Factory<T> {
public T create() throws Exception ;
}
class Pool<T> {
private Factory<T> factory ;
public T borrow() throws Exception {
T obj = getObjectFromPoolIfPossibleOrNullIfNot() ;
if (obj == null) {
obj = factory.create() ;
addToPool(obj) ;
}
return obj ;
}
public void return(T obj) {
// return the object to the pool
}
}
class ConnectionFactory implements Factory<Connection> {
public Connection create() throws SQLException {
return DriverManager.getConnection(url, props) ;
}
}
The trouble with this is that now your client code looks like this:
Pool<Connection> pool ;
try {
Connection conn = pool.borrow() ;
} catch (SQLException e) {
// deal with it
}
catch (Exception e) {
// uh ... can't actually happen? what do we do? swallow? declare?
// or this could be a RuntimeException
}
I would have done this:
public class CreationException extends Exception {
public CreationException(Exception e) {
super(e) ;
}
}
interface Factory<T> {
public T create() throws CreationException ;
}
class Pool<T> {
private Factory<T> factory ;
public T borrow() throws CreationException {
T obj = getObjectFromPoolIfPossibleOrNullIfNot() ;
if (obj == null) {
obj = factory.create() ;
addToPool(obj) ;
}
return obj ;
}
public void return(T obj) {
// return the object to the pool
}
}
class ConnectionFactory implements Factory<Connection> {
public Connection create() throws CreationException {
try {
return DriverManager.getConnection(url, props) ;
} catch (SQLException e) {
throw new CreationException(e) ;
}
}
}
With client code looking like this:
Pool<Connection> pool ;
try {
Connection conn = pool.borrow() ;
} catch (CreationException e) {
SQLException underlying = (SQLException)e.getCause() ;
// deal with it
}
Yes, you have to do a cast. But you can be absolutely confident that it
will succeed. You could use a similar approach with the 'throws Exception'
version, catching Exception and then casting to SQLException - if it
somehow isn't an SQLException, you'll be generating a ClassCastException
instead! However, i think my way is cleaner, and better documents what's
happening.
Now, with generics, there is another way:
interface Factory<T, E extends Exception> {
public T create() throws E ;
}
class Pool<T, E extends Exception> {
private Factory<T, E> factory ;
public T borrow() throws E {
T obj = getObjectFromPoolIfPossibleOrNullIfNot() ;
if (obj == null) {
obj = factory.create() ;
addToPool(obj) ;
}
return obj ;
}
public void return(T obj) {
// return the object to the pool
}
}
class ConnectionFactory implements Factory<Connection, SQLException> {
public Connection create() throws SQLException {
return DriverManager.getConnection(url, props) ;
}
}
Your client code now looks like this:
Pool<Connection, SQLException> pool ;
try {
Connection conn = pool.borrow() ;
} catch (SQLException e) {
// deal with it
}
No exceptions going unchecked, no spurious declarations. Perfect.
This does involve making the type declarations a bit more complicated, but
it means that definitions of factories and client code are both simpler. I
think this is a pretty good deal, myself.
The only shortcoming is that it doesn't deal that well with multiple
exceptions - if you have a factory which can throw either of SQLException
and IOException, you're a bit stuffed. There, you'd have to wrap them into
an AwkwardFactoryException, and declare that. Or you could declare Factory
and Pool with multiple type parameters for exceptions, but that way lies
madness - how many do you have? What do you do if you don't need all of
them? Until we have varargs for type parameters, that's not going to work.
tom
--
On Question Time last night, Tony Benn was saying that the way to solve
the low turnout at elections was to make voting compulsory. I think the
solution is for someone to start a political party that doesn't contain
wall-to-wall bastards. -- John Rowland