Saturday, January 16, 2016

Exception Throwing Etiquette

Nothing irritates me more than investigating reported exceptions where the developer didn't take the time to record the context with the exception.  That is, you get an error message like "Field XXX value not valid" or something like that.  What value was provided that was invalid?  What are the valid values for that field?  What were the other arguments to the method being thrown so that it's quick and easy to construct a test case for fixing the bug?  

These are all items of information that should have been reported with the exception.  Had they been reported with the exception, the developer assigned would have likely spent much less time diagnosing the issue and providing a fix.


You might ask why application architects should be concerned with code-level items such as these.  The reason is that architects are partly responsible for designing applications that are easy to support and help the organization keep support costs at bay.  Promoting and enforcing good exception etiquette is a cheap way to do that. 

Exceptions should contain context information to facilitate support.  This speeds support and problem resolution because it saves developers time.  That time savings allows developers to spend more time on feature development rather than support.  If your enterprise separates development and support, it allows the organization to spend less on support entirely.

Providing context with exceptions is easy for developers to do now.  Tooling exists now that makes this easy and safe.  I think the reason that developers don't take the time to include needed information with exceptions is laziness.  I'm going to present three options I see used most frequently.  That said, I'm sure that there are other tooling options.

Don't write custom formatting code for exceptions.  We've all see the issue of the exception reported was a null pointer from code that was in the process of formatting an exception message.  In other words, the exception that got reported was derivative; not the root exception that needs to be diagnosed.  The tooling options I'll tell you about reduce the risk of including context information with your exceptions as well as making it easier.

Apache Commons Lang Validation


A common reason for throwing exceptions is to validate arguments on public and protected methods.  I'll save a discussion on why all arguments for public and protected methods should be validated for another post. One of my favorite tools for validating arguments is the Validate utility from Apache Commons Lang.  Validate makes it very easy to both perform the validation and include context information along with the exception.  Examples follow which should make usage obvious.

Validate.notEmpty(firstName, "firstName can't be null or blank.  accountId=%s, 
    accountType=%s", accountId, accountType);

Validate.notNull(accountType, "accountType is required.  Allowed values are %s", 
    ArrayUtils.toString(AccountType.values()));

Validate.isTrue(startingBalance >= minimumBalance, "Starting balance must be larger than %s.  balance=%s, accountId=%s, accountType=%s", 
    minimumBalance, startingBalance, accountId, accountType);

Exception messages produced by the above examples are:


java.lang.NullPointerException: firstName can't be null or blank.  accountId=12345, accountType=PERSONAL

java.lang.NullPointerException: accountType is required.  Allowed values are {PERSONAL,BUSINESS}

java.lang.IllegalArgumentException: Starting balance must be larger than 1000.0.  balance=100.0, accountId=12345, accountType=PERSONAL


Validate is null safe, so you don't need to worry about derivative null pointer exceptions.

Some developers prefer the Javax Validator framework as it's more annotation based.  I provide information on that later in the post.

Apache Commons Lang Contexted Exceptions


Exceptions that are not the result of a validation, but a part of something more complex, such as a remote call, I use the ContextedRuntimeException from Apache Commons Lang.  Note that there is a checked ContextedException that operates exactly the same way.

The idea is that when you create the exception you also add context values that will automatically be present in the log when the exception is reported.  the exception is null safe, so you don't need to worry about passing it null context values.  An example should make usage obvious:


throw new ContextedRuntimeException("Error adding customer", rEx)
    .addContextValue("accountId", accountId)
    .addContextValue("accountType", accountType)
    .addContextValue("customerName", customerName);


When the exception is logged, you'll see something like this:



org.apache.commons.lang3.exception.ContextedRuntimeException: Error adding customer
Exception Context:
 [1:accountId=12345]
 [2:accountType=PERSONAL]
 [3:customerName=null]
---------------------------------
 at examples.commons.lang.context.ContextedExceptionTest.test(ContextedExceptionTest.java:18)

Javax Validator Framework


The Java Validator framework is popular in some organizations.  It's annotation based and is handy for validating inputs for published services automatically.  One of it's advantages is that since it's annotation based, it doesn't add code to the code base that needs to be tested.

One of the disadvantages to the Javax Validator framework is that it's much more difficult to include context information along with the exception thrown.  Yes, developers can provide explicit messages that will be used when the validation fails.  An example of including such a message follows:


public class CustomerAccount {
    @NotBlank(message="accountId can't be blank")
    private String accountId;
  
    @NotNull(message="accountType can't be blank")
    private AccountType accountType;
  
    @NotBlank(message="customerName can't be blank")
    private String customerName;
  
    @DecimalMin(value="1000.00", message="startingBalance must be at least $1000")
    private BigDecimal startingBalance;
}

Notice that in the examples above, all messages are explicit and hardcoded.  There's no easy ability to add in additional context information about other fields in the bean being validated so that a developer could get additional context to assist with diagnosing the issue.  Note that you also don't get the invalid value that violated the exception.

This presents an interesting question.  Would it be possible to use the Javax Validator framework and provide context information?  At first glance, you would have to write a custom class that implemented the MessageInterpolator interface (details here) and bootstrap it in (details here).  Maybe, I'll write one someday if I see enough of my clients utilizing this framework.  

2 comments:

  1. Another wonderful information, i find more new information,i like that kind of information,not only i like that post all peoples like that post,because of all given information was very excellent.
    Oracle SQL Training in Chennai

    ReplyDelete