Wednesday, November 11, 2009

Item 45:Include failure-capture information in detail messages




< BACKCONTINUE >


Item 45:Include failure-capture information in detail messages


When a program fails due to an uncaught exception, the system automatically prints out the exception's stack trace. The stack trace contains the exception's string representation, the result of its toString method. This typically consists of the exception's class name followed by its detail message. Frequently this is the only information that programmers or field service personnel investigating a software failure will have to go on. If the failure is not easily reproducible, it may be difficult or impossible to get any more information. Therefore it is critically important that the exception's toString method return as much information about the cause of the failure as possible. In other words, the string representation of an exception should capture the failure for subsequent analysis.



To capture the failure, the string representation of an exception should contain the values of all parameters and fields that "contributed to the exception."

For example, an IndexOutOfBounds exception's detail message should contain the lower bound, the upper bound, and the actual index that failed to lie between the bounds. This information tells a lot about the failure. Any or all of the three values could be wrong. The actual index could be one less than the lower bound or equal to the upper bound (a "fencepost error"), or it could be a wild value, far too low or high. The lower bound could be greater than the upper bound (a serious internal invariant failure). Each of these situations points to a different problem, and it greatly aids in the diagnosis if the programmer knows what sort of error to look for.



While it is critical to include all of the pertinent "hard data" in the string representation of an exception, it is generally unimportant to include a lot of prose. The stack trace is intended to be analyzed in conjunction with the source files and generally contains the exact file and line number from which the exception was thrown, as well as the files and line numbers of all other method invocations on the stack. Lengthy prose descriptions of the failure are generally superfluous; the information can be gleaned by reading the source code.



The string representation of an exception should not be confused with a user-level error message, which must be intelligible to end users. Unlike a user-level error message, it is primarily for the benefit of programmers or field service personnel for use when analyzing a failure. Therefore information content is far more important than intelligibility.



One way to ensure that exceptions contain adequate failure-capture information in their string representations is to require this information in their constructors in lieu of a string detail message. The detail message can then be generated automatically to include the information. For example, instead of a String constructor, IndexOutOfBoundsException could have had a constructor that looks like this:





/**
* Construct an IndexOutOfBoundsException.
*
* @param lowerBound the lowest legal index value.
* @param upperBound the highest legal index value plus one.
* @param index the actual index value.
*/
public IndexOutOfBoundsException(int lowerBound, int upperBound,
int index) {
// Generate a detail message that captures the failure
super( "Lower bound: " + lowerBound +
", Upper bound: " + upperBound +
", Index: " + index);
}


Unfortunately, the Java platform libraries do not make heavy use of this idiom, but it is highly recommended. It makes it easy for the programmer throwing an exception to capture the failure. In fact, it makes it hard for the programmer not to capture the failure! In effect, the idiom centralizes the code to generate a high-quality string representation for an exception in the exception class itself, rather than requiring each user of the class to generate the string representation redundantly.



As suggested in Item 40, it may be appropriate for an exception to provide accessor methods for its failure-capture information (lowerBound, upperBound, and index in the above example). It is more important to provide such accessor methods on checked exceptions than on unchecked exceptions because the failure-capture information could be useful in recovering from the failure. It is rare (although not inconceivable) that a programmer might want programmatic access to the details of an unchecked exception. Even for unchecked exceptions, however, it seems advisable to provide these accessors on general principle (Item 9).





< BACKCONTINUE >

No comments:

Post a Comment