Friday, February 27, 2015

Using parameter names to write self-documenting and self-diagnosing code, part 2

Identify the ultimate pre-condition.

Part one in this mini-series is found here.

Here is a simplified extract of a program I am working on right now.

A principal method:
public void process(Dto dto, PresumptiveOrder presumptiveOrderOrNull) {
    // some code
    commonExecute(dto, appointmentOrderOrNull);
    // some code
}

commonExecute:
protected void commonExecute(Dto dto, PresumptiveOrder presumptiveOrderOrNull) {
    dto.to(appointmentOrderOrNull);
}

to:
public void to(PresumptiveOrder presumptiveOrder) {
    presumptiveOrder.setProperty(getProperty());
    // set more properties
}

Now, this becomes interesting. The method process states that it is ok for it to get null as an order parameter. Then, it forwards this to  commonExecute, which also find that to be fine, and forwards it on to the to method in the dto, which is neutral as to what kind of parameter it will welcome. And that's where things start to get exciting.

For what happens if the order parameter is actually null, as allowed by the caller chain? Well, the statement presumptiveOrder.setProperty(getProperty()); will of course crash with a NullPointerException. So we have a problem to tacle. But how?

I will not discuss here the option of checking the parameter for null and then attempt some hopefully correcting or compensating actions. I frequently meet that attitude, sometimes under the name of Defensive Programming. I don't believe in that approach, but that's the subject for a different post. Instead, I promote an offensive style, based on the assumption that should this situation ever occur. it must be the result of a programming error (or a design error). Then, it becomes important to identify and correct that error in the code as soon as possible.

Two pieces are needed for this kind of errors to be avoided. Firstly, the caller of the method must know that a null parameter is illegal. Secondly, the method itself must detect and signal when it nevertheless does occur.

The first piece is solved by including this restriction in the name of the formal argument. So, I start by renaming presumptiveOrder to presumptiveOrderNotNull. Now, this important, semantic information is signalled to the caller from whereever he is calling this method, eg. through a tooltip like this:

The second piece is solved by including a precondition verification, as discussed in part one of this mini-series. The implementation of the method now becomes


public void to(PresumptiveOrder presumptiveOrderNotNull) {
    DbC.precondition(presumptiveOrderNotNull != null, "The order should not be null");
    presumptiveOrderNotNull.setProperty(getProperty());
    // set more properties
}

So far, so good.

Now, what about commonExecute calling to? With input argument presumptiveOrderOrNull, allowing an input value of null, there is no guarantee that the precondition for to is satisfied. So what is gained? Of course, what is gained is that it now becomes obvious that the input condition ...OrNull will not do, so it must be corrected. This will then push the stronger condition ...NotNull backwards through the caller chain, from the leave method, setting the ultimate pre-condition, to the principal method. The resulting calling chain then becomes

A principal method:
public void process(Dto dto, PresumptiveOrder presumptiveOrderNotNull) {
    DbC.precondition(presumptiveOrderNotNull != null, "The order should not be null");
    // some code
    commonExecute(dto, appointmentOrderNotNull);
    // some code
}

commonExecute:
protected void commonExecute(Dto dto, PresumptiveOrder presumptiveOrderNotNull) {
    DbC.precondition(presumptiveOrderNotNull != null, "The order should not be null");
    dto.to(appointmentOrderNotNull);
}

to:


public void to(PresumptiveOrder presumptiveOrderNotNull) {
    DbC.precondition(presumptiveOrderNotNull != null, "The order should not be null");
    presumptiveOrderNotNull.setProperty(getProperty());
    // set more properties
}

Now, the principal method process clearly signals that somewhere along the chain, there is a requirement for the order parameter not to be null. This allows this condition to be verified already at the outset, eliminating the risk for potentially tricky problems later on. And by requiring and verifying this input condition, each caller along the chain can confidently call the next method, knowing that ist pre-condition is satisfied.

In the next, and last, post in this mini-series, I will discuss the case of a polymorphic situation where some of the implementations have a stronger pre-condition than others.

No comments: