Yesterday Ugo started an interesting new thread -[RT] Checked exceptions considered harmful- on the cocoon-dev mailing list. Included is some recommended reading:
-  http://www.mindview.net/Etc/Discussions/CheckedExceptions
-  http://www.artima.com/intv/jdom3.html
-  http://www.artima.com/intv/handcuffs.html
-  http://www.theserverside.com/articles/article.tss?l=RodJohnsonInterview
-  http://www.artima.com/intv/solid.html
With the above links Ugo calls upon some 'authority' to ban checked exceptions from our Java code:
For me this was not only a revelation, but after giving it some thought also an unquestionable case of 'het kind met het badwater weggooien'
That is dutch literally saying 'throwing away the baby together with the water that washed him' and proverbally means: 'when cleaning out you shouldn't let your enthousiasm get the better of you. Before you know it you've thrown away the real valuables along. The challenge is to keep seeing clear even if those so-called real valuables were somewhat the cause of you needing to clean up in the first place. (Having a saying like that kinda makes us carefull people, no?)
Looking for the value of things: Checked Exceptions are quite essential in my view of a design-by-contract approach. Taking the anology with real life contracts: Writing down that "I'll deliver this service for this price". We get for method signatures:
- Service == return value and/or accountable state changes. (The guaranteed post-condition)
- Price == valid input arguments and/or a predescribed orchestration between methods. (The expected pre-condition)
if ( arg1 > something ) throw new IllegalArgumentException("arg1 shouldn't be this and that"); if ( badCurrentState() ) throw new IllegalStateException("Not ready to deliver service ABC." + "You should call/prepare XYZ first."); // what follows is the actual service code, below this point you have to live up to your end of the deal. // deliver the service!
Now we are in the situation where the caller lived up to what he needed to provide (the money) and there is nothing left but deliver as promised. There is a small nuance to make: We're expected to do a best effort (to counter for his effort). However the limitations of reality enforces some upfront decissions (foreseen memory space, foreseen file location and availability, foreseen/acceptable network delay,...) that might backfire during our best effort.
The challenge is to realize these limitations (decissions) and to openly communicate them to the customer during contract negotiation. Some of those problems he'll take away for you, adding it to his 'price' to pay and putting the responsibility to decide and handle on his side. For some of the problems you'll just agree upon some 'now this is a bit unlikely, isn't it?" to cover up for the "It's way to silly if I start checking all of that upfront: the price would be to high." And you settle for the opposite approach: "It's quite safe to assume all is well, instead of paying the price upfront." Which doesn't make you blind for the problems since we did acknowledge that things could still go wrong. So the settlement gets written down in the (typically called 'small print') exceptional cases part of the contract: We have just introduced checked excpetions.
So I have to agree with Ugo: checked exceptions are not a replacement for a good testing harnass. However that doesn't mean they have no value at all: I see it more a replacement for special case documentation that never got read. It's the machine enforced way to make you read the small-print sections of the contract, and provide some active way of dealing with them. On the same base, I don't see a conflict with what Leo entered into the discussion: Exceptions are (also) a signalling mechanism which are part of the normal execution. Just like small-print sections are a normal part of real life contracting.
Too bad, the story doesn't end here. Things get more complex, as they do in real life: enters 'SUBCONTRACTING'
As in real life, contract negotiations get more interesting when intermediaries are added to the mix: the clear communication between provider and beneficiary is fractured. The opportunity to easily look over the fence and see both sides of the issue, and find the optimal balance is dropped and replaced by the stress to do well for all, without actually having the time to consider all. Java interfaces are in this difficult position.
What goes on is this: a general (abstract) service contract is conceptualized (by an independant 3rd party?) even without actual users or providers for it in the neighborhoud. The stress is there to find a balance between:
- 1/ Long lists of meaningless exceptions refering to endless deep implementation details as this results in needless long lists of handling code at the caller side.
- 2/ Too little exceptions, because that leaves the subcontractor (implementation) in the frustrating position of needing to disguise his actual problem as a wrapped exception or a runtime exception.
To the consumer of the service it looks like the contract-designer had strong negotiations with (all potential) providers. And they've agreed to classify and group all possible detail thingies that can go wrong into those that need to be destinctly handled by him when 'normally' calling upon the service.
Reversely, to the provider of the service this same contract indicates that the customer explicitely negotiated with the contract-designer that he wants to be notified of certain meaningfull exceptional cases.
Assuming these considerations were made during the design of the contract I'ld have to conceed that the implementation frustration (what to do with all these exceptions) seems to naturally relieved in Leo's proposal (slightly rephrased)
- If the encountered problem during your best-effort-try to deliver the service naturally matches the 'meaning' of one of the declared exceptions in the contract, then you use that.
- If the encountered condition is proven 'big enough' for either side of the contract: throw an error.
- Else, make it a RuntimeException. (possibly wrapped)