When to Use Exceptions

Two of the biggest problems in most Java software are using exceptions (of either variety) improperly, or not using them at all. The failure to use exceptions properly accounts for more logic errors in production systems than any other single issue. The errors often result from forgetting to use exceptions or from declaring excessive custom exception types.

Forgetting Exceptions

When using the Exception classes, a common mistake made by many software engineers is to neglect the exceptions already provided in the JDK. When writing methods, the savvy developer often uses NullPointerException, IllegalArgumentExcep-tion, and the other built-in exception types. This allows the developer to precheck parameters to avoid corrupting data. The Java bean shown in Example 5-4 shows how forgetting to use these exceptions can be dangerous.

Example 5-4. Forgetting to use built-in exceptions
package oracle.hcj.finalstory;
public class ExceptionUsage {
 /** Storage for a customer name (Required). */
 private String customerName;
 public void setCustomerName(final String customerName) {
 this.customerName = customerName;
 }
 public String getCustomerName( ) {
 return this.customerName;
 }
}


Although everything looks okay here, there is a glaring omission. Note how the customer name is said to be required; however, the setter does not check the name coming in. If the user of this bean passes null or a zero-length string for customerName, the property would be in a corrupted state. You can prevent problems like this by checking every single parameter for data validity. The RuntimeException classes can help:

package oracle.hcj.finalstory;
public class ExceptionUsage {
 public void setCustomerName(final String customerName) {
 if (customerName == null) {
 throw new NullPointerException( );
 }
 if (customerName.length( ) == 0) {
 throw new IllegalArgumentException( );
 }
 this.customerName = customerName;
 }
}


This example is much better. You check the parameter customerName for all possible problems before you even worry about the body of the method. As an added bonus, with RuntimeException types, you do not have to declare these exceptions in a throws clause. Now, if the programmer passes invalid parameters to the method, the method will trigger an exception. This will generate a stack trace that will allow the programmer to resolve the issue during development instead of letting it sneak into production. Such extensive use of exceptions can be the difference between solid code and Swiss cheese.

Too Many Exceptions

On the other hand, it's possible to go a bit crazy with exceptions. Some developers declare all sorts of exception classes:

package oracle.hcj.finalstory;
public class GUIErrorException extends Exception {
}
public class CreditCardDeclinedException extends Exception {
}
public class BillingException extends Exception {
}
public class NoReservationException extends Exception {
}


Although there is nothing technically wrong with these classes, they are a bit excessive. If you were to continue this paradigm, you would have hundreds of exception classes in a real system, each with no content. Whenever you write a class that has no content, there should be a good reason to do so. In this case, there is no good reason—you could easily get a similar result with exception type constants and one exception class, as shown in Example 5-5.

Example 5-5. An exception class with type IDs
package oracle.hcj.finalstory;
public class MyCustomException extends Exception {
 public static final int GUI_ERROR = 0;
 public static final int NO_RESERVATION = 1;
 public static final int TRANSACTION_ERROR = 2;
 public static final int BILLING_ERROR = 3;
 public static final int CREDIT_CARD_DECLINED = 4;
 final int type;
 public MyCustomException(final int type) {
 if ((type != GUI_ERROR) && (type != NO_RESERVATION) && (type != TRANSACTION_ERROR) && (type != BILLING_ERROR) && (type != CREDIT_CARD_DECLINED)) {
 throw new IllegalArgumentException( );
 }
 this.type = type;
 }
 public int getType( ) {
 return this.type;
 }
}


If you declare a new exception class, make sure that the exception is fundamentally different than the other exceptions. Also, make sure that there isn't already an exception for the type of exception you need. For example, you shouldn't declare a business logic exception type for null pointers, but use NullPointerException instead.

Screenshot

In a contract for a telecommunications company, I saw a lot of empty exception classes. For practically every type of error, it had a specific exception class. I spent the next week refactoring and converting them to one exception class with type IDs that could be used to differentiate the problems. As a result, I reduced the code base by nearly 350 lines.


Using the new composite exception class is quite easy. Instead of throwing different types of exceptions, you merely pass a constant to the constructor of the composite exception class:

 public void billCard2(String cardNumber, Date expiration, Float amount)
 throws MyCustomException {
 // verify card type and expiration . . . if (!validCard) {
 throw new MyCustomException(MyCustomException.BILLING_ERROR);
 }
 // checking code . . . if (!approved) {
 System.err.println(cardNumber + "\t" + expiration + "\t" + amount);
 throw new MyCustomException(MyCustomException.CREDIT_CARD_DECLINED);
 }
 }


In fact, it is easier to use the composite class than the various empty exception classes. When using individual exception classes, you have to declare all of the thrown exceptions in a throws clause:

public void billCard(String cardNumber, Date expiration, Float amount)
 throws BillingException, CreditCardDeclinedException {
 // verify card type and expiration . . . if (!validCard) {
 throw new BillingException( );
 }
 // checking code . . . if (!approved) {
 System.err.println(cardNumber + "\t" + expiration + "\t" + amount);
 throw new CreditCardDeclinedException( );
 }
 }


Of course, if you change the business logic of the billCard( ) method in a way that introduces a new potential error, you will have to change the signature of the method to throw the new exception type. This means you may have to change all of the code that uses this method to account for the new method signature. Not only is this a time-consuming task, it is also frequently not possible. For example, if your product is a library used by your customers, such a change would break all of the software that your customers have written using the library. For these reasons, you should make sure that you create new exceptions only for fundamentally different things. For most business apps, one business logic exception class should suffice.

      
Comments