A DESCRIPTION OF THE REQUEST :
Method signatures in addition to "throws" section, should have a "suppresses" section that specifies which exceptions are suppressed by this method. For example if I have a method that does some I/O but doesn't want to declare IOException in its signature, it would look like this:
void doSomeWork() suppresses IOException
{
RandomAccessFile raf = ...;
raf.write(...);
raf.close();
}
This would be equivalent to
void doSomeWork()
{
try {
RandomAccessFile raf = ...;
raf.write(...);
raf.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Also
int getCount() suppresses IOException, MyAlgorithmException
throws MyException
{
int x = raf.length(); // RandomAccessFile.length()
return myAlgorithm(x);
}
is equivalent to
int getCount() throws MyException
{
try {
int x = raf.length();
return myAlgorithm(x);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (MyAlgorithmException e) {
throw new RuntimeException(e);
}
}
JUSTIFICATION :
It's not easy to say which exceptions should be declared as checked exceptions and which should be unchecked. In my opinion it depends on which abstraction layer you are coding in.
For example in low level libraries that directly use IO classes it makes sense for IOException to be checked. As soon as you go a tiny bit higher in abstraction layers it makes no sense to have IOException checked. It creates an awful coupling between the higher layer and the fact that the lower layer is actually doing IO (while a different implementation may keep everything in memory or database).
One way to avoid propagating the IOException statically in our code all way up to the application layer, is to catch it, log it and discard it (which I found very annoying; you have to have logging enabled and actually read through the logs to debug the code, etc.). A second way is to wrap it in another generic exception belonging to the higher layer and keep doing this between all the layers. Again very annoying because of all the messy code involved and in the end the client gets several exceptions wrapped in one another.
The reality is as soon as the exception leaves the layer it belongs to or at most the layer above, the code doesn't and shouldn't know much about the meaning of it. It has to be propagated all the way to the highest level and be dealt with there in a generic manner. So a third option is to wrap it in a RuntimeException and throw it. No one needs to care about this exception anymore and it's not discarded either. I found this to be much more elegant that the other two methods.
What's most annoying about IOException being checked is not that you have to deal with it after a call to RandomAccessFile.write(). It's having to deal with it because of calling MyStorage.store() which may or may not use RandomAccessFile. So we end up creating a StorageException and similar other ones just to wrap the exceptions of the lower layer, or even worse, we just log it and forget about it, or even more worse(!) just forget about it.
I found the proposed pattern so useful that I think supporting it at language level leads to much more elegant code for everyone.
|