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.