Thoughts on Exception Design

Before getting into the details of exception design, consider a facet that the simple examples in this chapter cannot communicate. Your overall exception handling strategy should guide the design and handling of your exceptions at the lowest levels. At least a napkin sketch of your exception handling architecture goes a long way to save you pain in the future. The following are some questions to consider.

• Will you handle exceptions at the top-most boundary layers such as UI, API, and service interfaces (my recommendation) or at lower levels in a more ad hoc fashion?

• In languages like Java in which the distinction is relevant, should you implement exceptions as checked exceptions that are declared as part of the method signatures or as unchecked exceptions?

• Are you allowed to swallow exceptions, preventing them from propagating, and if so, under what conditions?

• What are your strategies for converting or wrapping exceptions when the lower-level semantics become less appropriate for higher-level abstractions?

• Do you need to handle any exceptions differently, such as the need to handle InterruptedException in Java to deal with thread lifecycles properly?

Answering these and other questions will guide your exception design at the lower levels and minimize the confusion, inconsistency, and rework in your error handling strategy going forward.

Throughout this chapter, we have discussed various aspects of exception design to make exceptions and the code that uses them more testable. We have also identified or suggested a few additional benefits that we can derive from those same design considerations. Let’s take a brief detour from testing implementation patterns to summarize the design elements that we can apply to make our exceptions stronger overall. An example combining these design elements is shown in Listing 11-12.

1. Derive exceptions from an application-specific base exception. All exceptions for the application can be caught without catching system exceptions.

2. Create a hierarchy of categorically relevant base exceptions. Exceptions that communicate similar semantic concepts (e.g., boundary errors) or that share common operational contexts (e.g., I/O operations) should be grouped in a unified inheritance hierarchy.

3. Use contextually specific exception types. An exception communicates a message about a failure condition. The exception’s name should capture the meaning of the failure in a human-readable way, and the specificity of its type should make the failure programmatically isolatable and identifiable.

4. Use attributes to parameterize the exception’s intent. Many exceptions correspond to situations that can be characterized by one or more data values. Capturing the values as raw attributes assists us in programmatically responding to the failure when necessary.

5. Exception attributes should only be settable through the constructor to the greatest practical extent. The characterization of the error should not need to change. Enhanced contextualization should derive from further enclosing exceptions.

6. Represent an exception’s message as a literal format string constant. If no attributes are warranted, a simple literal string suffices. If attributes are present, the format string should format the attributes. This facilitates exact verification of scenarios involving complex string and data content.

7. If internationalization and localized error messages are required, the exception’s message constant should be the lookup key for the format string. Bind the locale as late as necessary to localize the error for the context.

Listing 11-12: Exceptions designed according to the recommended principles

public class MyApplicationException
    extends Exception {
  public MyApplicationException() {
    super();
  }

  public MyApplicationException(Throwable cause) {
    super(cause);
  }

  public String formatMessage(Locale locale) {
    throw new UnimplementedException();
  }
}

public class MyCategoryException
    extends MyApplicationException {
  public MyCategoryException() {
    super();
  }

  public MyCategoryException(Throwable cause) {
    super(cause);
  }
}

public ValueTooHighException
    extends MyCategoryException {
  public static final String MESSAGE_KEY = "value.too.high";
  private final int value;
  private final int threshold;

  public ValueTooHighException(int value, int threshold) {
    super();
    this.value = value;
    this.threshold = threshold;
  }

  public ValueTooHighException(int value, int threshold,
      Throwable cause) {
    super(cause);
    this.value = value;
    this.threshold = threshold;
  }

  @Override
  public String formatMessage(Locale locale) {
    String formatString = Dictionary.lookup(MESSAGE_KEY);
    return String.format(locale,
        formatString, value, threshold);
  }

  public String getLocalizedMessage() {
    return formatMessage(Locale.getDefault());
  }

  public String getMessage() {
    return formatMessage(Locale.ENGLISH);
  }
}

# The English dictionary entry for the message key
value.too.high =
  The value %1$d is higher than the threshold %2$d.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.12.140