A few days ago we had one of our Coding Dojo's at
Gutefrage.net. This time we tried to implement the "Roman Numerals kata".

I always learn a ton, solving such seemingly simple problems.

Have a look at our (unfinished) code

Here are the tests:

describe RomanNumeralsConverter do
  let(:converter) { RomanNumeralsConverter.new }

  def convert(number)
    converter.convert(number)
  end

  it 'returns an empty string for 0' do
    convert(0).should == ''
  end

  it 'returns I for 1' do
    convert(1).should == 'I'
  end

  it 'returns III for 3' do
    convert(3).should == 'III'
  end

  it 'returns IV for 4' do
    convert(4).should == 'IV'
  end

  it 'returns V for 5' do
    convert(5).should == 'V'
  end

  it 'returns VI for 8' do
    convert(8).should == 'VIII'
  end

  it 'returns X for 10' do
    convert(10).should == 'X'
  end

  it 'returns IX for 9' do
    convert(9).should == 'IX'
  end

  it 'returns XIV for 14' do
    convert(14).should == 'XIV'
  end
end

Here the implementation:

class RomanNumeralsConverter
  SPECIAL_VALUES = {0 => '', 5 => 'V', 10 => 'X'}

  def convert(number)
     result = ''

     while number > 3
       if SPECIAL_VALUES.include?(number+1)
         result += 'I'
         number += 1
       end

       nearest_boundary_under_number =
nearest_boundary_under_number(number)

       if nearest_boundary_under_number >= 0
         result += SPECIAL_VALUES[nearest_boundary_under_number]
         number -= nearest_boundary_under_number
       end
     end
     number.times do
       result += 'I'
     end
     result
  end

  def nearest_boundary_under_number(number)
    SPECIAL_VALUES.keys.delete_if {|i| i > number }.max
  end
end

Duplication in tests

We tried to remove duplication from the tests. I think we failed.
One of my teammates pointed out that PHPUnit has
DataProviders for this kind of test data.

I didn't think that this was a good idea - but looking at the tests now I have to admit that
there is virtually no value in writing the tests spec style for this kind of problem.

Write the simplest tests first

We had quite a hard time with the substraction rule (4 => IV).
We wrote a test for the 4 very early - I think we could have made it
easier for us by first ignoring the substraction rule and focusing on
all cases where it does not apply.

  • Write the easy tests first
  • If you hang try another test

Accept weired code

We "cheated" quite a bit using an array for the special cases. It just
didn't look right. Supprisingly it came together at the end of the session anyway.

  • Accept weired code

When to abstract

Right at the beginning one of my colleagues pointed out, that we just
could create a big array with a mapping between all the arabic number to
the roman numerals and use that. I think he had a point..

But since we wanted to create an algorithm that was no option. We
started with a big if / elsif / else statement. That was fine for 2 or
3 tests, but then we were moving sideways. No algorithm was going to emerge.

That's when you need to stop and think about how to abstract a little bit further.

  • Identify when you are moving sideways - then start abstracting

The end

No world moving lessons - but valuable non the less. Thats why I like coding dojos -
they are a good place to reason about the nature of programming, without
having to write production code.