|
|
Remember C/C++. Remember malloc, calloc, free, new, delete. You do? Good, because If you remember them, then, perhaps, you also remember how important they are, and their purpose: memory (de)allocation was your own responsibility and that they were a part of the programming logic. They were not syntactically required. Case in point, when programming in C/C++, you took care of memory yourself and that was required for your program to run and behave properly.
Remember Java? Yes that’s the language most of us are programming in, these days. Remember exceptions? Of course you do. They are your responsibility! The importance of exception handling in your program is right up there with the importance of memory management in C/C++ programs. Lot of times they are syntactically required, but it is just too easy to work your way around it: Bury it! Do you? Hopefully not. Because that would be a crime, just like accessing an un-initialized pointer in a C program.
Without spending more original thoughts/sentences about programming philosophy here (that would be text for another article!), let me humbly suggest productive ways of handling exceptions in your programs. Now, I am not an authority on exception handling, or even programming. These are techniques I use and that I have learnt over a period of time, practicing exception handling and reading articles. By no means is this supposed to be a comprehensive coverage on the topic of exceptions. Do yourself a favor, give the last section “References and Suggested Readings” a second look!
Exceptional Design!
Exception Handling deserves thought as early as design time. Specifically, when identifying business objects in the system, one must always question if there is a possibility of a business object being unable to perform its function. Furthermore, one must also ask questions about the destiny of the exception raised under such circumstances. How must other business objects handle an instance of such an exception? Whether such business failures would lead to a “user-friendly” error message to the user or is the system capable of solving the problem - by fixing it or resorting to an alternate process? A design with such questions (and answers) in mind lays the foundations of a fault-tolerant, possibly even a “self-healing” system.
Classification by Syntax: Checked Exceptions and Unchecked Exceptions
Typically all exceptions you throw must be declared in your method signature. Exception to this rule is the RuntimeException. You can throw one when you like, without explicitly declaring that your method throws it. Also, any method you call potentially can throw this exception; the java compiler does not require you to check for this exception. Hence “Unchecked Exceptions”. Usually these exceptions are used in system errors: division by zero, deferencing a null pointer, bounds checking failure. Mostly you need not worry about them. By intent, such exceptions are not meant to be handled by the client. In general, if you encounter these when your code executes, it is probably a good idea to refactor it.
Classification by Meaning: Technical Exceptions and Business Exceptions
Technical exceptions arise out of technical failures. There’s a whole slew of them… “file not found”, “class not found”, “socket can not connect”, “database transaction failed”. Just to name a few. Business exceptions arise out of failures in business actions. Consider these business exceptions - “Configuration missing”, “Plug-in not found”, “Remote service unavailable” and “System busy, Please try your request later again”. One thing is clear, users do not care about technical exceptions. If you pay attention, you will notice a direct mapping between the technical exceptions and the business exceptions mentioned above. Subsequently, another thing becomes clear – all technical exceptions must translate to business exceptions before they reach the user. Business exceptions may also arise out of logical failures: “you do not have enough money in your account to buy your requested item: an Airbus A-340”.
Throwing an exception
So when you run into an error condition, what exception should you throw? Remember, the purpose of instantiating an exception object is not only to reflect a real-life physical or logical error condition, but to also provide as much detail as possible about this error condition. An exception class is just like any other class, except that one of its ancestors is the Throwable class. Several things become information to the client when it will catch this exception: The exception class name, the exception class package, the stack trace stored in it, any public properties you provide as extra information. Try to use pre-existing exception classes, if you can fully express the error condition with that class, otherwise you need to create a new exception class that addresses the need for you to be able to fully express the error condition. Consider a descriptive name. Another particularly useful guideline in choice of which exception to throw is: You should favor throwing exceptions which belong to the package that propagates them. This avoids undesirable package dependencies.
Catches win matches; judging the exception
Fielders in the game of cricket never have to make a decision about whether to catch, and what to do after catching. You, as a Java programmer, have an advantage though, you tell the compiler about your intent to catch, and it is guaranteed! So what must you do when you catch an exception? You judge its meaning. The first point of judgment is based on the exception class itself. You may choose to ignore it (you do not try to catch it, so compiler makes you declare that you throw it). You may choose to catch, wrap and propagate (you create a new exception – one which reflects your perspective of the problem, but as details, contains the original exception object) or you could handle it (for ex. File not found in /home/foo/etc, try with /etc next). Most well designed exception classes provide a good description of what happened using exception properties. This information, when applied to your well defined business logic, becomes a basis of what needs to be done when you want to catch, wrap & propagate or handle the exception.
Encapsulation
One important observation to make when you ignore an exception/propagate it – by declaring that you throw it, is that the client becomes aware/ must be aware of this particular exception class. Done carelessly, this can lead to breaking encapsulation in your design. For instance, you write a data access layer, which can fetch data from a database. Needless to say, if the client needs to be aware of the exceptions you encounter (ex. the ubiquitous SQLException), then you have failed to encapsulate the business function of your object. Perhaps a new exception class needs to be written: DataAccessExeception or something akin to that.
On a closing note for this article, I once again invite you to read the articles mentioned in “References and Suggested Readings” section below.
References and Suggested Readings
- Best Practices for Exception Handling
http://www.onjava.com/pub/a/onjava/2003/11/19/exceptions.html - Best Practices in EJB Exception Handling
http://www-106.ibm.com/developerworks/java/library/j-ejbexcept.html - Beware the dangers of generic exceptions
http://www.javaworld.com/javaworld/jw-10-2003/jw-1003-generics.html - Performance improvement techniques in Exceptions
http://www.precisejava.com/javaperf/j2se/Exceptions.htm
