Guidelines on Using Exceptions in Java

General Usage Never use exceptions to handle the normal logic and flow of control for the program - use exceptions only for exceptional conditions. Use the try-catch-finally of exception handling instead of logic statements (if, while, case, etc.) to handle the exceptions which should not, you hope, occur - e.g. system is out of memory. Use logic statements for the various conditions in the business process which you know will occur - e.g. a payment is less than the amount owed. Ensure that your call stack is eventually prepared to handle any exceptions that may get thrown by your code. Do not allow exceptions to unwind up the call stack without being handled and result in a crashed main program. Ensure that your code honors the "catch or specify" requirement for exception handling. Any block of code that could throw an exception must be enclosed either in a try-catch-finally block that handles the exception or a within a method that uses the "throws" clause to specify the exceptions it will pass up the call stack. Do not wrap checked exceptions, write do nothing catch blocks or otherwise by-pass handling of exceptions. Bypassing the exception handling only helps the code compile but doesn't help (and actually hurts) the run-time reliability of the program. Don't program the method or class until you know how you will handle the required exceptions. Don't write empty exception handlers so that you can compile your application code to get around the compiler requirement for exception handlers. If you haven't figured out the exception handling, as well as the normal application logic, you aren't ready to write the method or class. Don't write an empty exception handler so that you can "get back to it later" - work on it at the same time as the associated application logic. Implement a consistent style for handling common exception types across your program - IOException, DivisionByZero, etc. For example, implement a consistent behavior in user-facing applications for handling the FileNotFoundException. Avoid in some cases prompting the user to re-enter a filename and in other cases only doing a log and abort. Exception Types Checked exceptions must adhere to the "catch or specify" requirement. Exception handling for checked exceptions must be able to handle the exception and continue operation of the program. Ensure that your programs manage state such that when a checked exception occurs the program can revert to a prior safe state and continue operation. Ensure that your programs handle the unchecked exceptions (Error and RunTimeException) at some level as well even though this is not required by the Java API. This improves both the reliability of your programs and the internal documentation of the code for subsequent maintenance. In both cases, ensure that your program shuts down gracefully - releases any open resources, returns the system to a prior known and safe state, etc. For unchecked exceptions, programs may handle exceptions high up in the call stack or broadly delegate the exception handling to avoid adding extensive error-handling code for unchecked exceptions that might unduly clutter the functional purpose of the code. Do not implicitly reduce the fidelity of exception information by using handlers of high-level classes such as Exception or RunTimeException when exceptions of more detailed types can occur. If you chose to generalize exception handling, do so explicitly. While this does not change the logic of the exception handling, it makes the intent of your code more clear to other programmers who may maintain this code later. Logging Exceptions Logging for checked exceptions should not generally be necessary since the exception handling mechanisms should be able to handle the exception and proceed. Unchecked exceptions (errors and run time exceptions) should be logged. Programs should log exceptions using the default logging mechanism for the project. Do not improvise logging on an ad hoc basis. Logging information should include the type of exception that occurred, when the exception occurred, unwrap any chained exception information, and capture information about the program state that can support corrective actions. Logging for for error exceptions should include information to identify the source of the problem - e.g. out of memory. Logging for runtime exceptions should include information to identify and correct the program code error. Try-Catch-Finally When catching exceptions, ensure your catch statements are hierarchically complete and ordered. Catch low-level exceptions first, catch higher level exceptions next, and then catch the top-level exceptions (including type Throwable) last. Use your exception handling for high-level exception classes instead of letting this slide into the finally block as a "catch-all" for exceptions. Always provide and use the finally block - even if it appears unnecessary. It does no harm if unused and is good safety net if your catch logic is incomplete or if an exception situation occurs that you didn't anticipate. The finally block should also be used to "clean up" after any statements in the try block . Use the finally block to prevent resource leaks by ensuring that the resource is always released. Also use the finally block to ensure that the program returns to a known, predictable, and safe state. Don't ignore exceptions - e.g. don't write do-nothing catch blocks. At a minimum, that an exception occurred should be logged usefully. Use the try-catch-finally and throws options for exception handling at a consistent level of abstraction. For example, an aggregate class of several object types should not have an exception type in some cases handled by the contained object and in other cases by the aggregate. If this occurs, re-structure your code to reconcile this disparity. Throwing Exceptions Include in the throws clause of your methods both the checked and unchecked exceptions thrown by the method. Do this even though it is not generally required by the compiler because it improves the documentation of the code for future revision and maintenance. Throw exceptions appropriate to the abstraction of your class and keep your exception classes appropriate to the associated class types in your program - e.g. associate FileNotFoundExceptions with file handling classes, IOExceptions with I/O classes, ArrayOutOfBoundsException with array classes, etc. Do not write code that throws exceptions of type Throwable, Error or Exception. These are too general to be meaningful to the exception handler. Use one of the more specific exception classes. Using these highest level types indicates your code won't bother to tell the user anything about what the problem is. Do not write code that throws the exception NullPointerException. This is an exception that programmers expect to be thrown by the Java Virtual Machine (JVM) and not from custom code. Pick another defined exception type or write your own. Chained Exceptions When one exception leads to another, always chain the exceptions together. Allowing related exceptions to exist as distinct, unchained, objects abandons useful information about the behavior of the system that created the exceptions. Ensure that the call stack eventually evaluates exceptions to see if they are chained and provides exception handling logic based upon the results. At a minimum, the logging system should capture the un-chained details from the exceptions. User Defined Exceptions Write your own exception classes only if you can demonstrate one of the following conditions are true: a.) You can justify a need for an exception type that isn't provided by the Java API (or a vendor API), b.) you can demonstrate that other programmers using your code need to be able to discriminate between the exceptions you throw and those provided by the Java API (or from classes provided by a vendor), c.) you have a need to package you code without dependencies on other packages for exception types, d.) or your code throws several exception types but has only one semantic meaning when it does this (or the reverse). When writing your own exceptions, follow the conventions of the Java language for checked and unchecked exceptions. Use checked for recoverable exceptions and unchecked for errors or runtime problems that are not recoverable and should lead to a shutdown of the program. For any classes you write that are used as exception classes, append "Exception" to the name of the class. Declare all fields of exception classes as "final" or "private". The data carried in objects of exception classes should not be modified and should be protected from unauthorized or accidental modifications.

No comments: