Given a language that enforces checked exceptions (let us assume Java), how do we design an API that can throw exceptions without violating the Law of Demeter?
To put this question in the proper scope, let us assume we are writing a publicly accessible jar library. Some of the classes will be the public-facing API: others are internal bits and pieces not meant to be directly used outside the library.
For example, perhaps this library has the responsibility of serializing objects through a stream or writer: the interfaces for OutputStream and Writer specify that output operations may throw the checked exception IOException. The specific stream/writer implementation is irrelevant: IO exceptions may be thrown.
Let us further assume that the write operations do not occur in response to a method call that one would reasonably assume results in an IO operation, e.g. saveTo(File)
or sendTo(URL)
.
If the library cannot reasonably recover from the exception, it must pass it down the call stack to the appropriate level where it may be handled. However, this leaks information about distant parts of the system, violating the Law of Demeter.
It appears as though there are three ways to deal with this situation.
Declare the method and all methods that call it as throwing the checked exception, all the way to the API boundary. The client code must then deal with the checked exception whether it wants to or not. However, the exception is at least documented.
This is a bit brittle: implementation details deep in the library dictate the public interface of other classes. This is pretty much what the Law of Demeter tells us to avoid.
Wrap checked exceptions in an unchecked exception:
try { ... } catch (SQLException e) { throw new UncheckedException(e); } catch (IOException e) { throw new UncheckedException(e); }
This fixes the interface dilemma, but puts another burden on client code: it must know to look for unchecked exceptions, which in this case may not be your typical logic bug (e.g. null pointer, divide by zero) but an exception case that can be reasonably handled by code. However, it is now no longer part of the public interface, and if it is, it is optional. Furthermore, all checked exceptions are now in a single unchecked class, making it difficult to catch one but not another.
Wrap each checked exception which may be thrown in a dedicated unchecked exception:
try { ... } catch (SQLException e) { throw new UncheckedSqlException(e); } catch (IOException e) { throw new UncheckedIoException(e); }
This fixes the interface problem, allows each exception type to have its own unchecked exception type, but results in an explosion in the number of exception classes required.
How can we design a public API in such a way that checked exceptions can be thrown without violating the Law of Demeter, where classes far away (the client code) know too much about the inner workings of the library that publishes said public API, while still being able to deal with error conditions that arise in the library?