sliming and refactoring and deliberate duplication



Suppose I'm doing the Print-Diamond kata in Ruby:
Given a letter print a diamond starting with 'A' 
with the supplied letter at the widest point. 
For example: print-diamond 'E' prints
    A
   B B
  C   C
 D     D
E       E
 D     D
  C   C
   B B
    A
I start with a test
def test_diamond_A
  assert_equal ['A'], diamond('A')
end
which I pass using
def diamond(widest)
  ['A']
end
I add another test
def test_diamond_B
  assert_equal [' A',
                'B B',
                ' A'], diamond('B')
end
which I pass using
def diamond(widest)
  if widest == 'A'
    return ['A']
  end
  if widest == 'B'
    return [' A',
            'B B',
            ' A']
  end
end
I add one more test
def test_diamond_C
  assert_equal ['  A',
                ' B B',
                'C   C',
                ' B B',
                '  A'], diamond('C')
end
which I pass using
def diamond(widest)
  if widest == 'A'
    return ['A']
  end
  if widest == 'B'
    return [' A',
            'B B',
            ' A']
  end
  if widest == 'C'
    return ['  A',
            ' B B',
            'C   C',
            ' B B',
            '  A']
  end
end
The tests have already proved valuable:
  • I've decided I don't want to actually test printing
  • I've chosen the result format - an array of strings
  • I've chosen not to embed newlines at the end of the strings
  • I've something to refactor against
However, there is no point in carrying on sliming. As the tests get more specific, the code should get more generic. I have three specific tests, but the code is equally specific. I need to generalize the code.

While coding the array of strings for the 'C' case I found myself copying the result for 'B' and modifying that. Specifically, I had to:
  • duplicate the 'B B' string
  • add a space at the start of the ' A' and 'B B' strings
  • add a new middle string 'C C'
This gave me the idea to try a recursive implementation. My first step was to refactor the code to this:
def diamond(widest)
  d = inner_diamond(widest)
  mid = d.length / 2
  d[0..mid-1] + d[mid+1..-1]
end

def inner_diamond(widest)
  if widest == 'A'
    return ['A',
            'A']
  end
  if widest == 'B'
    return [' A',
            'B B',
            'B B',
            ' A']
  end
  if widest == 'C'
    return ['  A',
            ' B B',
            'C   C',
            'C   C',
            ' B B',
            '  A']
  end
end
This looks a promising step towards a recursive solution - to make the implementation of 'C' contain the implementation of 'B' and then add strings only for 'C'. So, remembering what I had to do when copying and modifying, I refactored to this:
def inner_diamond(widest)
  if widest == 'A'
    return ['A',
            'A']
  end
  if widest == 'B'
    return [' A',
            'B B',
            'B B',
            ' A']
  end
  if widest == 'C'
    b = inner_diamond('B')
    upper,lower = split(b.map{ |s| ' ' + s })
    c = widest + '   ' + widest
    return upper + [c,c] + lower
  end
end

def split(array)
  mid = array.length / 2
  [ array[0..mid-1], array[mid..-1] ]
end
From here I verified the recursive solution works for 'B' as well:
def inner_diamond(widest)
  if widest == 'A'
    return ['A',
            'A']
  end
  if widest == 'B'
    a = inner_diamond('A')
    upper,lower = split(a.map{ |s| ' ' + s })
    b = widest + ' ' + widest
    return upper + [b,b] + lower
  end
  if widest == 'C'
    b = inner_diamond('B')
    upper,lower = split(b.map{ |s| ' ' + s })
    c = widest + '   ' + widest
    return upper + [c,c] + lower
  end
end
Now I worked on generalizing the use of the hard-coded argument to inner_diamond() and the hard-coded number of spaces:
def inner_diamond(widest)
  if widest == 'A'
    return ['A','A']
  end
  if widest == 'B'
    a = inner_diamond(previous(widest))
    upper,lower = split(a.map{ |s| ' ' + s })
    n = (widest.ord - 'A'.ord) * 2 - 1
    b = widest + (' ' * n) + widest
    return upper + [b,b] + lower
  end
  if widest == 'C'
    b = inner_diamond(previous(widest))
    upper,lower = split(b.map{ |s| ' ' + s })
    n = (widest.ord - 'A'.ord) * 2 - 1
    c = widest + (' ' * n) + widest
    return upper + [c,c] + lower
  end
end

def previous(letter)
  (letter.ord - 1).chr
end
Now I collapsed the duplicated specific code to its more generic form:
def inner_diamond(widest)
  if widest == 'A'
    return ['A','A']
  else
    a = inner_diamond(previous(widest))
    upper,lower = split(a.map{ |s| ' ' + s })
    n = (widest.ord - 'A'.ord) * 2 - 1
    b = widest + (' ' * n) + widest
    return upper + [b,b] + lower
  end
end
Finally some renaming:
def inner_diamond(widest)
  if widest == 'A'
    return ['A','A']
  else
    inner = inner_diamond(previous(widest))
    upper,lower = split(inner.map{ |s| ' ' + s })
    n = (widest.ord - 'A'.ord) * 2 - 1
    middle = widest + (' ' * n) + widest
    return upper + [middle,middle] + lower
  end
end
To summarise:
  • When sliming I try to think ahead and choose tests which allow me to unslime the slime.
  • If I have slimed 3 times, my next step should be to unslime rather than adding a 4th gob of slime.
  • My first unsliming step is often deliberate duplication, done in a way that allows me to collapse the duplication.


Experiential Learning 1: Beginning

is an excellent book by Jerry Weinberg. There's no isbn - you can buy it from Leanpub. As usual I'm going to quote from a few pages:
This is a very simple exercise, but it will get people talking about process improvement.
If there is no provocation, there is no learning. ... We must first put our students into a provocative environment. We must encourage them to experiment - to play with the materials in that environment.
In many traditional courses, the only significant observation made is pass/fail on the test.
By the mathematical properties of averaging, almost all teams will perform "better" than almost all individuals - simply because they are more average. … All we are measuring is how much closer an average answer is likely to be to another average answer which is essentially a tautology.
People are more ready to accept your facts than your opinions, so be very careful to separate observation (news) from interpretation and significance.
Be patient with silence. Usually a long silence comes just before a breakthrough idea.
Different people on each team learned different things from the same trial of the same exercise. This is characteristic of well designed and well led experiential exercises.
Leaders are not in complete control of what participants are going to learn. Are you going to be able to live with that?
The strongest way to achieve safety in experiential exercises is by making clear that every exercise is optional. If someone doesn't want to participate, they are always free to step aside without explaining their reasons, and without any attempts to persuade or cajole. If someone wishes to opt out, then the learning leader should invite them to take an observer role, but they may opt out of that, too.
You can't just pop experiential exercises at people regardless of the context, so pay special attention to the very first exercise you do with a group.
There must be a bazillion ways to form teams, but we've tried only half of them.

The principles of product development flow

is an excellent book by Donald Reinersten (isbn 978-1-935401-00-1). As usual I'm going to quote from a few pages:
Operating a product development process near full utilisation is an economic disaster.
When we emphasise flow, we focus on queues rather than timelines.
Almost any specialist can become a queue.
We grow queues much faster than we can shrink them.
When queues are large, it is very hard to create urgency.
Queues amplify variability. Moving from 75 to 95% utilisation increases variability by 25 times.
Sequential phase-gate processes have inherently large batch transfers.
Large batches encourage even larger batches.
Reducing batch size is usually the single most effective way to reduce queues.
Companies inevitably feel they can computerise this whiteboard, however, they almost always create a more elegant but less useful system.
The speed of feedback is at least two orders of magnitude more important to product developers than manufacturers.
The human effect of fast feedback loops are regenerative. Fast feedback gives people a sense of control; they use it, see results, and this further reinforces their sense of control.
Homeostasis is the tendency of a system to maintain its current state.
In product development, our problem is virtually never motionless engineers. It is almost always motionless work products.
Opportunities get smaller with time, and obstacles get larger.
The scarcest resource is always time.
To align behaviours reward people for the work of others.
It has been said that one barbarian could defeat one Roman soldier in combat, but that 1,000 Roman soldiers could always defeat 1,000 barbarians.
The Marines, and all other elite organisations, maintain continuity in their organisational units.

Olve's uncle

Yesterday, whilst stuck in a traffic jam, my great friend Olve Maudal told me a wonderful story about his uncle. Olve's uncle lived in the country. He didn't much care for the city. He felt the city folk were always in a rush. Often too busy to remember basic courtesy. One day he had to go into the city so he got into his old Volvo and set off. In the middle of the city the old Volvo stalled. He tried restarting it several times with no luck. Then the driver behind him starting tooting his horn. Olve's uncle calmly opened his door, got out his car, and walked towards the tooting driver. Olve's uncle explained to the driver that his old Volvo wouldn't start, and, offering him the car key, asked him if he knew how to get it started. The driver immediately, and in a friendly manner, said yes and took the key. As the tooting driver started walking towards the old Volvo, Olve's uncle said, "I'll wait here and toot your horn for you".

practising

Last week I attended a two day course on the banks of the River Tay learning to speycast using a 15 foot double handed rod. I learned it quickly and effectively, for three reasons:
  • First, I was taught by a great tutor, Gary Scott. Not only is Gary a world champion spey caster he is also a really great teacher and a thoroughly nice bloke to boot.
  • Second, I had almost no previous experience of fly-fishing for trout. This was very helpful as I had no bad habits to unlearn. In contrast, some of the other anglers who attended Gary's course were experienced trout fly-fishers. Gary would correct some particular movement and for a few casts they would do the new movement - but then Gary would move along to help the next angler and soon they dropped back into muscle-memory-mode and had lost the new movement.
  • Third, when I started Gary made sure I did not have a hook tied to the end of the line. This would have been plain dangerous before I had at least some control. Having a small piece of wool instead of the hook meant I was not thinking about catching a salmon; I was thinking only about improving my casting technique. In contrast the other anglers on the course started with a hook and their efforts to improve their casting were inevitably watered down by their desire to catch a salmon.