Agile Zone is brought to you in partnership with:

I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 638 posts at DZone. You can read more from them at their website. View Full User Profile

My use case for checked exceptions

01.31.2012
| 7691 views |
  • submit to reddit

Checked exceptions are an idiomatic Java feature that has been questioned by many in the last years: throws clauses specify the possible errors raised by a method, and the calling code is forced to deal with them at compile-time, by wrapping the call into a try/catch construct or by adding a throws clause too.

However, dynamic languages and also statically typed ones like C# do not force the immediate handling of exceptions.

The dangers of throws clauses

There are some technical issues with checked exceptions: the produce unreachable code sometimes, when the implementations do not throw the exceptions defined by their interfaces (Misko Hevery makes the example of the ByteArrayOutputStream).Other issues are methodological: checked exceptions produce an inflation of try/catch blocks, which proliferate and start to be ignored by programmers quickly. Moreover, there is no recovery from many exceptions (at least for the current thread of execution), so making every single piece of code specify how to handle the error seems an overkill.

An alternative to checked exceptions is to exercise the code with a comprehensive test suite, that contains tests for corner cases and error conditions. But you can't blame the hammer for smashing your fingers: every tool has use cases where it fits and cases where it doesn't. Except for the fact that tha Java API is full of checked exceptions, so you're forced to use an hammer if you want to read a file.

A note on distributed computing

It is widely believed that it is impossible to extend transparently the local computation paradigm (made of calls to functions and methods) to the realm of distributed computing. Remote Procedure Call and Remote Method Invocation are a useful abstractions, but they can't presente an interface identical to the one of local methods. The famous paper A note on distributed computing explains the underlying issues of distribution that can't be dealt with automatically:

  • latency in remote calls is several orders of magnitude higher than with local ones.
  • Memory access in low-level languages like C is problematic, since memory cannot be shared with remote processes. You can't pass a pointer to a server, although in the case of virtual machines Virtual Proxies more or less work.
  • Concurrency is everywhere, and there is no way to provide synchronization automatically with synchronized and common multithreading patterns, unless you adopt specific algorithms.
  • Partial failures are possible. Locally a failure usually results in the termination of the whole process; distributing the computation means not only failures are more likely due to the unreliable channels, but they also encompass only a part of the system, and the surviving parts should continue to behave correctly and deal with it (indeed distribution's job is often masking failures of individual components). You don't want your database master server to fail because it failed to update a slave.

Thus distributed computing cannot be made similar to local computing, unless we choose one of two options:

  • make the local paradigm similar to the distributed one; this solution would needlessly introduce difficulties like checking failures that will never happen locally during your lifetime. Catching RemoteExceptions on all your method calls is not funny.
  • make the distributed paradigm similar to the local one, overseeing frequent failure modes and the problems specified above. What happens during these cases would be undefined.

Checked exceptions come in

Some objects hide network communication, by containing references to TCP sockets and streams, by sending UDP packets, or by making HTTP calls. Implementations like RMI do this automatically, but the substance doesn't change.

We need to bridge these objects with local computation. For example, if we are implementing a distributed search algorithm we want to simply cut away the node that do not respond, showing the user only the other results. We try to mask remote failures by guaranteeing basic functionalities.

I found this approach helpful in connecting local and remote objects:

  • objects that work with the network throw checked exceptions. These exceptions are part of the interface just as the return type.
  • Bridge objects deal with partial failure, synchronization, and security; they attempt to mask failures or performance issues and provide a basic correct behavior.
  • Objects that work over bridges do not need to care about the network, and they can be tested in isolation.

Checked exceptions are a way to ensure we're not mixing up the two models: you are forced to deal with potential failures deriving from the network at compile time. It's the exception job to propagate into the signatures of the methods until we handle it; it's impossible to call a method using the network without notice.

I saw a similar idea for "formal" verification at DDD-Day last year by Giacomo Tesio, where domain models used checked exceptions to express failures. The idea is this kind of failures (distributed ones, or relevant errors in the business domain) are too important to be left only to testing coverage. The preference is to sacrifice the code's flexibility to ensure architecture is not screwed up by objects calling the server inside a local computation (like a comparison for a sorting algorithm or a result filtering).

Code sample

In my example I implemented a search targeting multiple remote servers. The interface of the objects dealing with remote computation declares a checked exception in its clause:

public interface RemoteDocumentsSearcher {

    QueryResult query(Query query)
            throws NoResponseException;

}

Internally, the Java socket exceptions are wrapped into new ones, representing the failure modes the application is interested with. The Only mock types you own may apply also to exceptions: I prefer to establish my own hierarchy, made of classes where I can add methods and fields.

try {
            return reader.readLine();
        } catch (SocketException e) {
            throw new ConnectionClosedException("Connection closed while reading.");
        } catch (IOException e) {
            throw new ConnectionClosedException("Connection closed while reading.");
        }

In the bridge object, the error handling consists of simply excluding the current server from the results, but it could be anything in principle; for example, in the case of file fetching from multiple sources it consists of setting up a new connection to one of the surviving servers. The important thing is to produce a behavior that is composable with the rest of the process instead of bubbling up as an exception, or a RuntimeException:

        QueryResult result = new QueryResult();
        if (valid()) {
            try {
                QueryResult additionalResult = searcher.query(this);
                result = result.merge(additionalResult);
            } catch (NoResponseException e) {
                // exclude not responding neighbors from the search
            }
        }
        return result;

The bridge object implements this interface. In this case it is very similar to the original one without exceptions, but it doesn't necessarily be: the behavior of an object masking failures can be less powerful than the original one.

public interface DocumentsSearcher {
        public abstract QueryResult searchDocument(Query query);

}
Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Mason Mann replied on Tue, 2012/01/31 - 8:50am

Making exceptions part of the interface makes evolving public interfaces EVEN harder. 

Dumb idea that doesn't work in practice.

Jonathan Fisher replied on Tue, 2012/01/31 - 12:25pm

I disagree with the first commenter. I think this is an acceptable use case, mainly because it provides a clean abstraction from the actual transport. How often does one pass an object reference as a parameter in a function and modifying the object? Have you ever used error codes? I consider both of those bad practices, but checked exceptions provide you a compile-safe way of handling each scenario.

cowwoc replied on Tue, 2012/01/31 - 1:02pm

Checked exceptions make perfect sense when you understand what they're for: handling *expected* failures that the programmer *cannot* prevent ahead of time. The final caveat is that there is no point for a method to throw a checked exception if the programmer cannot reasonably do anything useful with it (for example, SQLException) short of logging and crashing horribly.

 

Once you understand that, there are plenty of good places where checked-exceptions should be used.

Mason Mann replied on Tue, 2012/01/31 - 2:53pm in response to: cowwoc

" Once you understand that, there are plenty of good places where checked-exceptions should be used."

 

No.

There's a reason why *modern* languages abolishes exceptions altogether. Go, for instance. Once *you* understand *that*, there are no good places where checked-exceptions should be used.

Mason Mann replied on Tue, 2012/01/31 - 2:55pm in response to: Jonathan Fisher

You missed the point, though. The problem is that public interfaces are very hard to change. Adding exceptions to the signature doesn't exactly help.

It's crap. Anders Hejlsberg et. al. understood that when designing C# and you should really read up on their rationale (http://www.artima.com/intv/handcuffs.html for instance.)

Erwin Mueller replied on Wed, 2012/02/01 - 11:47am

But you are changing the interface anyway, if you add another exception. The difference between unchecked exceptions and checked is: in the first case your program will just crash, if it's not handling the new exception; in the last case the compiler will prevent you from releasing buggy code. In both cases you have changed the interface, only in the first case you have done it silently and hidden, but in the last case you documented it.

I think Sun make a big mistake in creating checked and unchecked exceptions: they should have named them differently. A unchecked exception should have the name Bug, like NullPointerBug, or something similar. Because unchecked exception are thrown if there is a bug in your code.

From a bug you cannot recover, you should fix it and update your program. From an (checked) exception you can recover, like a FileNotFoundException.

From a SQLException you can recover, why not. Just use some default values, use a different database, let the user enter a new database, create the database and populate it with default values, etc.

But from a NullPointerException you cannot recover, because you have assumed that a value is a valid reference but it is null. How can you recover from that? It's a bug in your code.

So, Anders Hejlsberg opinion, is that because every programmer is just bad and ignores good practice on exception handling, C# is just going to ignore best practice, too. Just catch all exceptions, and show the user a "There was an Error, please continue" dialog and be done with it. What happens if you catch a NullPointerException and you show just an Error-Dialog so the user can just continue to use the application? The user will think the application and data is fine, but it is not.

In fact, after a NullPointerException your data will have a null reference somewhere. That is your data just went bad. The user will think your application and his data is o.k. because you just showed him a dialog that he can just ignore. After he saves his work and exit the application he comes back and all his data is gone.

The scalability issue is solved in Java. You can wrap the exceptions you can't recover from in the low level module to a module specific exception and re-throw it to a higher level module.

 

Lund Wolfe replied on Sat, 2012/02/04 - 8:11pm

Checked exceptions are usually proper expected exceptions that need to be handled at some point. The problem has to be thrown back up the chain to somebody if the called method doesn't handle it.

It is very powerful but too complicated for many programmers. Some languages, like C#, have traded power for an easier learning curve.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.