Tuesday, February 5, 2008

How Can a Simple Code Template Change Make Life Better?

Ever felt like making a small change has sent you down a rabbit hole? I sure have. Consider the following classes:


public interface NotificationService {
void sendNotification(String user);
}

public class Unit {
private NotificationService notification;

public Unit(NotificationService notification) {
this.notification = notification;
}

public void doSomething(String username) {
notification.sendNotification(username);
}
}


You might write a test to ensure that Unit.doSomething() passes off the username to the NotificationService.sendNotification(). This would be a very simple mock test.

Now, let's assume that everything shown so far is already under test, 100% coverage. Requirements have changed, and now when we doSomething(), we should check for pending notifications for that username first.

The first change to make would be adding a method to the NotificationService interface:


public interface NotificationService {
void sendNotification(String user);
Notifications retrievePendingNotifications(String user);
}


Now, we can write a test for the Unit class, using a mock of NotificationService to verify that doSomething() will check for the pending notificiations. Doing Test-Driven Design, we want to make sure that we are focusing on changing one class at a time. However, now that we have added retrievePendingNotifications() to the interface without actually implementing that method on the implementing classes, we will have a compile error.

One choice for getting around this would be to go down the rabbit trail, writing tests for the implementing classes and actually implementing the new method. Personally, I don't like to do this because it changes my focus. I would rather first ensure that the caller is correct before worrying about the actual implementation of the interface.

Most modern IDE's will offer a quick-fix to stub out the implementation of the interface so that things will compile. The default behavior for this default implementation is typically to return the simplest thing possible. If the function is void, then the stubbed method does nothing. If the function returns a primitive, say int, then the IDE will put in something like return 0; to make things compile.

This kind of default stub has already worried me that I will forget to actually do the implementation. In fact, I have done just that before, sometimes not finding the hole until a day or two later. At that point, figuring things out sometimes involves going to the debugger to figure out why I'm getting a NullPointerException.

Enter code templates. The default implementation can be modified in most IDE's. Here is a sample of how I changed my code template to prevent this problem in the future:


public Notifications retrievePendingNotifications(String user) {
throw new NotImplementedException(
"NotificationServiceImpl.retrievePendingNotifications() not yet implemented.");
}


Now, I don't have to try and keep that little piece of information in my head. I can rest easy that, if I forget to implement something, it will still fail (hard) during integration testing. Now, I will know the direct cause of the problem immediately.

It's amazing to me how this simple change has made me rest so much easier.

Go forth and write tests!

No comments: