no scaffolding means we're done

Suppose I'm doing the print-diamond kata in cyber-dojo in Java. I start with a test
    @Test
    public void diamond_A() {
        String[] expected = {
            "A"
        };
        String[] actual = new Diamond('A').toLines();
        assertArrayEquals(expected, actual);
    }
I slime a solution as follows
public class Diamond {

    private char widest;

    public Diamond(char widest) {
        this.widest = widest;
    }

    public String[] toLines() {
        return new String[]{ "A" };
    }
}
now I add a second test
    @Test
    public void diamond_B() {
        String[] expected = {
            " A ",
            "B B",
            " A ",
        };
        String[] actual = new Diamond('B').toLines();
        assertArrayEquals(expected, actual);
    }
and I slime again as follows
public class Diamond {

    private char widest;

    public Diamond(char widest) {
        this.widest = widest;
    }

    public String[] toLines() {
        if (widest == 'A')
            return new String[] { 
                "A" 
            };
        else
            return new String[] {
                " A ",
                "B B",
                " A ",
            };
    }
}
Like all techniques this approach has a certain style. In this case there is a small but definite asymmetry between the specificness of the tests (one for 'A' and another one for 'B') and the slightly less specificness of the code (an explicit if for 'A' but a default everything-else for 'B'). This is a style that is relaxed about the asymmetry, a style that emphasises this step as merely one temporary step on the path of many steps leading towards something more permanent. A style that recognises that code, by it's nature, is always going to be more general than tests.

But suppose you get hit by a bus tomorrow. How easily could your colleagues tell that this code was work in progress? Code that was not finished?

As an experiment I thought I would try an alternative style. One that is not so relaxed about the asymmetry. One that tries a bit harder to be more explicit about distinguishing code that's a temporary step still on the path from code that has reached its destination.

First I wrote a test that expresses the fact that nothing is implemented.
    @Test(expected = ScaffoldingException.class)
    public void scaffolding() {
        new Diamond('Z').toLines();
    }
I make this pass as follows
public class Diamond {

    private char widest;

    public Diamond(char widest) {
        this.widest = widest;
    }

    public String[] toLines() {
        throw new ScaffoldingException("not done");
    }
}
The scaffolding, as its name suggests, will have to be taken down once the code is done. While it remains, it indicates that the code is not done. Now I start. I add the diamond_A test (as before) and make it pass
public class Diamond {
    ...
    public String[] toLines() {
        if (widest == 'A') {
            return new String[]{ "A" };
        }
        throw new ScaffoldingException("not done");
    }
}
I add a second diamond_B test (as before) and make it pass
public class Diamond {
    ...
    public String[] toLines() {
        if (widest == 'A') 
            return new String[] { 
                "A" 
            };
        if (widest == 'B') 
            return new String[] {
                " A ",
                "B B",
                " A ",
            };
        throw new ScaffoldingException("not done");
    }
}
The scaffolding is still there. We haven't finished yet. Now suppose I refactor the slime (by deliberately duplicating) and end up with this
public class Diamond {
    ...
    public String[] toLines() {
        if (widest == 'A') {
            String[] inner = innerDiamond();
            String[] result = new String[inner.length-1];
            int mid = inner.length / 2;
            for (int dst=0,src=0; src != inner.length; src++)
                if (src != mid)
                    result[dst++] = inner[src];
            return result;
        } 
        if (widest == 'B') {
            String[] inner = innerDiamond();
            String[] result = new String[inner.length-1];
            int mid = inner.length / 2;
            for (int dst=0,src=0; src != inner.length; src++)
                if (src != mid)
                    result[dst++] = inner[src];
            return result;
        }
        throw new ScaffoldingException("not done");
    }
}
The code inside the two if statements is (deliberately) identical so I refactor to this
public class Diamond {
    ...
    public String[] toLines() {
        if (widest == 'A' || widest == 'B') {
            String[] inner = innerDiamond();
            String[] result = new String[inner.length-1];
            int mid = inner.length / 2;
            for (int dst=0,src=0; src != inner.length; src++)
                if (src != mid)
                    result[dst++] = inner[src];
            return result;
        } 
        throw new ScaffoldingException("not done");
    }
}
Now I add a new test for 'C'
    @Test
    public void diamond_C() {
        String[] expected = {
            "  A  ",
            " B B ",
            "C   C",
            " B B ",
            "  A  ",
        };
        String[] actual = new Diamond('C').toLines();
        assertArrayEquals(expected, actual);
    }
This fails. I make it pass by changing the line
        if (widest == 'A' || widest == 'B')
to
        if (widest == 'A' || widest == 'B' || widest == 'C')
Now I remove the if completely
public class Diamond {
    ...
    public String[] toLines() {
        String[] inner = innerDiamond();
        String[] result = new String[inner.length-1];
        int mid = inner.length / 2;
        for (int dst=0,src=0; src != inner.length; src++)
            if (src != mid)
                result[dst++] = inner[src];
        return result;
        throw new ScaffoldingException("not done");
    }
}
And it no longer compiles.
I have unreachable scaffolding.
Time for the scaffolding to come down.
I delete the throw statement.
public class Diamond {
    ...
    public String[] toLines() {
        String[] inner = innerDiamond();
        String[] result = new String[inner.length-1];
        int mid = inner.length / 2;
        for (int dst=0,src=0; src != inner.length; src++)
            if (src != mid)
                result[dst++] = inner[src];
        return result;
    }
}
Now the scaffolding() test fails. I delete that too.
We're green.
The scaffolding is gone.
We're done.

2 comments:

  1. Hi Jon,

    I like this idea! Have you thought about how to implement this in a language without exceptions?

    ReplyDelete
  2. Hi Mike,
    I haven't. It's a good question. Maybe for a language like C you could use setjmp/longjmp in some way? It would be an interesting to try it out...

    ReplyDelete