Skip to content.

Manageability

Sections
Personal tools
You are here: Home » blog » stuff » 13 Exceptional Exception Handling Techniques

13 Exceptional Exception Handling Techniques

Document Actions

As I read through the onjava article "Best Practices for Exception Handling" I got this sinking feeling, are these truly best practices or could there be even better practices out there?. The author appears to be a seasoned professional however if this is all we can say about handling exceptions then we really are in bad shape.

Fortunately, ever since the exception debate was kicked off years ago, I have been compiling effective solutions that I have come across.

If you expect to read a comprehensive introduction on exception handling you can find that elsewhere (i.e. ChurchHillObjects). I'm not going to spend time regurgitating through the basics. However a recent basic piece worth mentioning is "Three Rules for Effective Exception Handling. The piece has a few general guiding principles. Paraphrasing and extrapolating from that article, put as much useful information in your exceptions, don't let errors propagate through code undetected and catch when you have enough context to handle an exception correctly.

Well here's a list of some more specific techniques on how to manage your exception handling mess:

  1. Declare Unchecked Exceptions in the Throws Clause - Make it easy for programmers and the IDE to discover the RuntimeExceptions that may be thrown by your method.
  2. Soften Checked Exceptions - Wrap Checked Exceptions inside RuntimeExceptions thus providing a mechanism for tunneling the exception through higher layers. This prevents the anti-pattern of having long chains of checked exceptions.
  3. Always Preserve the Stack Trace - Make sure that the stack trace of a wrapped exception is captured and preserved. Be careful with distributed systems, as simplistic use of Java serialization requires the Exception classes be available in all tiers, furthermore pre JDK 1.4 Exceptions do not preserve the stacktrace. The single worse thing one can do after eating your exceptions is to to capture it but throw away all the essential details.
  4. Exceptions Should Not Kill the Thread Unnoticed Use the uncaughtException handler provided by ThreadGroup to detect and handle when threads have terminated unexpectedly.
  5. Provide Enough Context - When defining a new Exception don't just add a new error message, provide enough context for a catcher to gracefully handle the exception. If an existing Exception class doesn't have essential context (i.e. FileNotFound ) provide a class that wraps it and provides the essential context.
  6. Be Afraid of Finally Failing - Finally is usually recommended to guarantee resource cleanup, however you should yourself guarantee that finally never fails. In general, ensure finally never eats up your original exception.
  7. Build Default and Provide For Custom Error Handlers - Many times it is necessary for a client class that doesn't directly call another class to affect exception handling at the point of failure. Many frameworks provide handlers that can be attached to classes (for example JCIFS) that permit either default or custom overriding of exception handling logic. Therefore, replace hard coded catch blocks with delegation
    try{...}
    catch(IOException ex){
          Category.getInstance("data.jdbc.provider").error("sorry",ex);
          }
    --->
      try{...}
      catch(IOException ex){
         handler.handle("sorry",ex);
         }
    
    Some words of wisdom from the Spring framework:
    The org.springframework.jdbc.core package, uses callbacks to move control - and hence error handling and connection acquisition and release - from application code inside the framework. Spring uses a similar callback approach to address several other APIs that involve special steps to acquire and cleanup resources, such as JDO (acquiring and relinquishing a PersistenceManager), transaction management (using JTA) and JNDI.
    Also, as an additional service to your clients, provide some reasonably good default exception handling logic.
  8. Declarative Exception Handling - Dynamically change exception handling policy. Handling exceptions is an extremely dynamic concern, provide a flexible framework to manage this. For example Struts allows user defined ExceptionHandlers to be associated on a local or global scope using configuration instead of code. In otherwords, responses to exceptions should not only based on type but also on context in where they are called.
  9. Avoid Violating Liskov's Principles - Support polymorphic behavior also in your exception handling code. The most straight forwared way is to catch and wrap all exception that expose implementation details. For example:
    Why does the Vector class throw an ArrayIndexOutOfBoundsException when all other List interface implementors throw IndexOutOfBoundsException? Doesn't this somewhat defeat the purpose of encapsulation, since by throwing ArrayIndexOutOfBoundsException, the Vector class is exposing it's internal implementation?? The ArrayList is also based on an array data structure....but it doesn't hint it by throwing the more specific exception. Why can't they deprecate the method and throw the correct exception?
    public void boo( ) throws SQLException , 
                              JMSException , NamingExcetion
      {
      ...
      }//em
     
    -->
    public void boo( ) throws DAOException 
      try/catch & re-throw (wrapped) Exception
      }//em
    
    Encapsulate low level throws list with wrapped Execption
  10. Ensure Multiple Tier Information Propagation Unless a full recovery can be made - an Exceptions needs to propagate to the presentation tier. Also ensure that Exceptions are serializable so that they can be made available across multiple tiers. Furthermore, if an Exception class cannot be made available across all tiers, wrap that exception class with one that can and preserve the original's stack trace.
  11. Support Consistent User Communication - Name, Number, Importance, Response, Message should be consistent. Exceptions should also record information that is useful for addressing the problem as well as information that is useful for advising the debugger or the human user. Internationalization should also be supported. Also try to spend more time on useful error messages that show people why the error occurred and give a clue as to how to fix it.
  12. Use Command Patterns for Complex Error Handling Logic - Continuations or commands with retry strategies can be used (or service combinators) to create robust ways of handling expected errors. William Grosso explores the Command Pattern wrt to Exceptions in more detail here. The use of Command Patterns allow for more reuseable exception handling logic. For example, you can build a libary of service combinators to support the following:
    (1)	Sequential s ? t
    (2)	Concurrent s | t
    (3)	Timeout t , s
    (4)	Retry (s)
    (5)	Non-termination stall
    
  13. Logically Asychronous Calls Should Not Throw Exceptions - Provide support where the failure of an asychronous call may be programatically retrieved using a "asychrnonous token". (see Correction Loop)
    The Observer shouldn't throw exceptions. There is no-one to catch them. The protocol could catch and ignore them, but that's dangerous. The Observable doesn't know anything about the Observers - that's the point of the pattern - so is not interested in their failure.

I've got a lot more that I should list but haven't. I just felt that 13 would be the most appropriate number for this list. Anyway, wouldn't it be nice if all these techniques were packaged in one convenient framework? Even better, wouldn't it be nice if we had a good conceptual framework for handling exceptions?


Last modified 2004-01-12 12:53 PM

 

Powered by Plone

This site conforms to the following standards: