Friday, December 17, 2004

Designing with Exceptions

Rob Diana recently wrote about Exceptions and APIs, some thoughts stemming from a thread on Test Driven Development he read. On the StraightTalking-Java mailing list this spawned a lengthy discussion about the usage of exceptions, when they should be internally handled vs. thrown (possibly wrapped). My thoughts on the subject are a bit more general, but still worth mentioning (especialy since I'm trying to utilize this blog on a more consistent basis, and this seemed like a good way to start - again...)

Any object or set of cohesive objects (in some cases, an API) should only throw exceptions that are at the same level of abstraction as the interface to the object(s)/API.
IOW, the abstraction level of the object's interface must be consistent - that is not negotiable - and the thrown exceptions are part of the interface. Thus, the exceptions must match the abstraction of the rest of the interface (parameter types, return types, etc.)

If we follow this rule (it is more than a guideline, IMO) the decision about what kinds of exceptions to throw becomes easy in 99% cases. An app's persistence layer does not throw JDBC errors or hibernate errors, because those are implementation details of that layer - it is written at a "higher" level of abstraction. However, it can reasonably trap those kinds of errors and perform whatever reasonable action the developer chooses (which might include, for example, retrying the operation an extra time), most of the time wrapping the underlying error and throwing some exception that matches it's interface.
Same for a "business logic" layer - it talks in terms like CustomerAccount, Product, BillingCycle - so it can also use notions like CustomerNotFoundException, ProductDiscontinuedException, and BillingCyclePassedException (if the designer so chooses). It would not, however, use notions like SQLException, IOException, EJBException, etc. Those are implementation details - you would not want to change your whole interface just because the implementation changed from using straight JDBC to something else, for example.

I'm surprised there would be any debate about this. APIs (or any objects/set of cohesive objects) are certainly capable of handling some kinds of errors internally, but many times the context of the call is best able to know what to do.