Showing posts with label dependency. Show all posts
Showing posts with label dependency. Show all posts

Andy and the crusty cobs

My friend Andy works on the railways. He used to work as a master baker at one of the big supermarkets. One of the items he baked was Crusty Cobs.

Clearly customers can only buy Crusty Cobs if there are some Crusty Cobs actually on the shelves. So the supermarket also employed a bakery supervisor to check Crusty Cob availability. (Naturally the supervisor was paid more than Andy. After all he was the supervisor.) Every day, at 4pm, the bakery supervisor arrived, clutching his clipboard, and counted the Crusty Cobs on the shelves; one, two, three, four, five, six, seven... Andy got praised if there was good Crusty Cob availability and chastised if there wasn't.

So Andy adapted. He baked extra batches of Crusty Cobs but waited to put them on the shelves until shortly before 4pm. Crusty Cob availability, as recorded by the supervisor, was very good. Customers wanting Crusty Cobs before 4pm were often disappointed.

A short while later as well as counting the Crusty Cobs on the shelves (always at 4pm), the supermarket also started counting the number of Crusty Cobs actually sold at the tills during the day. Now Andy also got praised if lots of Crusty Cobs were sold.

So Andy adapted. As well as sticking the Crusty Cob labels on the Crusty Cobs, he also printed extra Crusty Cob labels and stuck them on some Soft Rolls (same price). After all, who looks at the labels? So Crusty Cob sales, as recorded at the tills, were always very good. The number of Crusty Cobs actually bought by customers was somewhat less.

Not long after, the supermarket noticed a drop in the sales of Soft Rolls. They asked Andy not to bake so many Soft Rolls.

So Andy adapted. He baked less Soft Rolls, labelled them correctly, and put the extra Crusty Cob labels on the French Sticks. Customers wanting Soft Rolls, at any time, were often disappointed.

Goodhart's Law states:

When a measure becomes a target, it ceases to be a good measure.

Or, more colloquially:

Targets are where measures go to die.


Adapt Parameter and Preserve Signature

Working Effectively With Legacy Code by Michael Feathers (isbn 0-13-117705-2) is a great book. The very first dependency breaking technique (p326) is called Adapt Parameter. It uses this Java example:



public class ARMDispatcher
{
    public void populate(ParameterSource request) {
        String[] values
            = request.getParameters(pageStateName);
        if (values != null && values.length > 0)
        {
            marketBindings.put( 
                pageStateName + getDateStamp(),
                values[0]);
        }
        ...        
    }
    ...
}


Michael writes:

In this class, the populate method accepts an HttpServletRequest as a parameter. ... It would be great to use Extract Interface (362) to make a narrower interface that supplies only the methods we need, but we can't extract an interface from another interface. ...


and a bit later...

Adapt Parameter is one case in which we don't Preserve Signatures (312). Use extra care.


Well, here's a variation on Adapt Parameter where we do Preserve Signatures. I'll stick with the Java example, but the idea is broadly applicable...

First we create our ParameterSource interface and fill it with the signatures of the methods in HttpServletRequest that our populate method calls:

public interface ParameterSource
{
    String[] getParameters(String name);
}
then we implement the adapter for HttpServletRequest:
public class HttpServletRequestParameterSource 
    implements ParameterSource
{
    public HttpServletRequestParameterSource(HttpServletRequest request) {
        this.request = request;
    }

    public String[] getParameters(String name) {
        return request.getParameters(name);
    }

    private HttpServletRequest request;
}
Next we add an overload of populate taking a ParameterSource and implement it with an exact copy-paste:
public class ARMDispatcher
{
    public void populate(ParameterSource request) {
        String[] values
            = request.getParameters(pageStateName);
        if (values != null && values.length > 0)
        {
            marketBindings.put(
                pageStateName + getDateStamp(),
                values[0]);
        }
        ...        
    }

    public void populate(HttpServletRequest request) {
        String[] values
            = request.getParameters(pageStateName);
        if (values != null && values.length > 0)
        {
            marketBindings.put(
                pageStateName + getDateStamp(),
                values[0]);
        }
        ...        
    }
    ...
}
Now we Lean on the Compiler (315) to make sure we haven't missed any methods in HttpServletRequest that our populate method calls. Add any we missed to the ParameterSource interface. Implement them in HttpServletRequestParameterSource as one liners. When it compiles we can refactor to this:
public class ARMDispatcher
{
    public void populate(ParameterSource request) {
        String[] values
            = request.getParameters(pageStateName);
        if (values != null && values.length > 0)
        {
            marketBindings.put(
                pageStateName + getDateStamp(),
                values[0]);
        }
        ...        
    }

    public void populate(HttpServletRequest request) {
        populate(new HttpServletRequestParameterSource(request));
    }
   ...
}
And now we have a seam we can pick it at...
public class FakeParameterSource 
    implements ParameterSource
{
    public String[] values;

    public String[] getParameters(String name) {
        return values;
    }
}