This is my favourite way of introducing functional decomposition, at best when already familiar with TDD. And it works as an eye-opener because TDD, when applied ’blindly’ on this problem, can easily lead to convoluted solutions in spite of any attempts to refactor.

So, we’re to generate something like this:

   A   
  B B  
 C   C 
D     D
 C   C  
  B B  
   A   

For contrast, start off in the ’typical’ approach of TDD – start with A, then B, then C, … -, have a good struggle, then stop at half-time, assess the outcome and restart. This time begin by precisely describing the problem statement in words, as if explaining over the phone. What we expect, is…

Now we can take on the slices one by one, beginning with the sequence of letters. Being in Ruby, we’re going to simply extend the String class.

class String
  def sequence_letters
    'A'.upto(self).reduce(&:+)
  end
end

Short and sweet! Then we move on to the second slice, noting that the input here can be any string.

class String
  def spread_diagonal
    chars.each_with_index.map { |c, i| (' ' * (length - 1)).insert(i, c) }.join("\n")
  end
end

Short and sweet! Then we move on to the third slice, noting that this too works on any string.

class String
  def mirror_left
    each_line.map { |l| l.chomp }.map { |l| l.reverse + l[1..-1] }.join("\n")
  end
end

Short and sweet! Then we move on to the final slice, noting that this too works on any string.

class String
  def mirror_down
    self + "\n" + each_line.map { |l| l.chomp }.to_a.reverse[1..-1].join("\n")
  end
end

Short and sweet! And now all that’s left is composing these, leaning back and marvelling at the clarity.

class String
  def diamond
    self.letter_sequence.spread_diagonal.mirror_left.mirror_down
  end
end

Now, there are many ways to solve the challenge, but this solution is incredibly powerful due to…

And this is a great time to reopen the question you’ve surely discussed before, how much thinking is one supposed to have in TDD. This exercise is a clear demonstration that functional decomposition may be hard to attain on the mechanical, heads-down, YAGNI path, and that breaking down the problem in a divide-and-conquer approach can lead to a smooth flow of well-compartmentalised deliverables, each with clear responsibilities and interfaces, resulting in a "good design".

PS: being a fan of Elixir’s pipe operator, |>, the perfect tool for any data transformation pipeliner, I couldn’t help but had to make this look better still, so after patching String with…

  def >>(method)
    self.send(method)
  end

I could write…

  def diamond
    self >> :letter_sequence >> :spread_diagonal >> :mirror_left >> :mirror_down
  end

Code is art!