Atom Feed SITE FEED   ADD TO GOOGLE READER

Be Minty #2: Aggressively enforce constraints

Part 2 in a series.

Suppose we have a simple interface that can initiate a delivery:
interface DeliveryService {
/**
* Schedules {@link order} for immediate delivery to {@link destination}.
*
* @return a snapshot of the delivery in progress.
*/
Delivery scheduleDelivery(Order order, Address destination);
...
}

Naturally there are several possible implementations of this interface. Implementations might make a remote call to a distant servers, store database records, or even send instant messages to the delivery people on duty. And conveniently, the caller doesn't care how the method is implemented, just that it works.

Since interfaces often outlive their implementations, it helps if the interfaces tightly constrain both their inputs and their outputs. Suppose that the initial implementation of DeliveryService is tolerant of a null country in the destination address. Over time clients may come to require that the service implementation is tolerant of null for the country.

And much later on, when it is time to change the implementation, we painfully discover that the new implementation needs a valid country and that the clients don't supply it. We're faced with the gross options of either fixing all of the misbehaving clients, or figuring out how to get around knowing the country value.

By aggressively enforcing API constraints, you make it easier to write high-quality clients. It's helpful to enforce constraints both on the input arguments and the output return value:
  Delivery scheduleDelivery(Order order, Address destination) {
checkPrecondition(!order.getProducts().isEmpty())
validateAddress(order.getStoreAddress());
validateAddress(destination);

Delivery delivery = postInitiateDeliveryRequest(order, destination);

checkPostcondition(delivery.getDeliveryId() > 0);
checkPostcondition(delivery.getOrder().equals(order));
checkPostcondition(delivery.getDestination().equals(destination));
return delivery;
}


Wrapping Up


Design-by-contract is just a good habit. By constraining the number of possible states it makes future maintenance easier. Plus there's fewer weird cases to consider if things go wrong.

Be Minty #1: returning Collections

In this series I document patterns, techniques and styles for designing fantastic APIs in Java. My target audience is software developers working with big teams on big projects. Many of the suggestions should be overkill for a weekend project. Topics in this series will range from simple style issues to broad application design strategies.

Aside from my packaging, nothing is original - I take inspiration from Effective Java, Kevin B, Zorzella Z, and the rest of my fine coworkers.

My first Minty API initiates and tracks deliveries. Let's look at one interface method in detail:
  /**
* Returns all deliveries that have started but not yet completed.
*
* @return a possibly-empty List.
*/
public List<Delivery> getDeliveriesInProgress(long customerId);

Ordering must be specified


This API does not specify the ordering of the returned list. This is a problem. Suppose a caller takes the returned list and immediately displays it in a table on screen - a reasonable thing to do. Now the user interface is dependent on a subtle implementation detail of the method - the ordering of the deliveries. If the method just-so-happens to return deliveries ordered by ID, and the IDs just-so-happen to be assigned chronologically, then the UI will probably look great! The deliveries will be oldest to newest.

But now reasonable changes to the implementation - such as how IDs are assigned, or how the list is ordered - break the UI. An awkward UI isn't the only possible problem here. Other calling code might (incorrectly) assume that the most recent delivery is always last in the list, so business logic could break if the ordering changes.

The fix is simple but requires prudency - always specify the ordering of a returned collection in Javadoc:
  /**
* Returns all deliveries that have started but not yet completed.
*
* @return a possibly-empty List, ordered by delivery-requested time.
*/
public List<Delivery> getDeliveriesInProgress(long customerId);

And of course, your implementations need to provide this behaviour.

Returned values must be immutable


By not trusting the caller of your API, you can prevent them from introducing subtle bugs. Consider this calling code:
    List<Delivery> deliveries = deliveryService.getDeliveriesInProgress(customerId);

/* don't show expedited deliveries on this screen */
for (Iterator<Delivery> d = deliveries.iterator(); d.hasNext(); ) {
Delivery delivery = d.next();
if (delivery.isExpedited()) {
d.remove();
}
}
showDeliveries("Standard shipping", deliveries);

This caller is directly mutating the returned value from our method. Returning mutable types allows for subtle bugs. If the implementation doesn't always return a newly-allocated list, callers could get results without expedited deliveries.

Instead, returned collections should always be immutable. Google Collections makes this easy with the immutableList() method.

Wrapping Up


Big applications are hard because bugs live in the details. If you return collections often, these patterns will save bugs.