A while ago I started exercising on the Roman numerals kata, whose problem is about converting from a positional to a non-positional representation:
1 => I 2 => II 3 => III 4 => IV 5 => V ...
After J.B. Rainsberger reviewed my astronaut solution during his course, I found out there were still some points to address and duplication to be removed; so I set out on performing the kata again with reuse in mind. As always, spoilers ahead.
The basic solution
A classic solution for the kata is while cycle that builds the representation by recursively subtracting these quantities from the number to be converted:
100 => C 90 => XC 50 => L 40 => XL 10 => X 9 => IX 5 => V 4 => IV 1 => I
I could actually start from 1000 => M; this series supports numbers up to 399.
There are two things that emerged from discussing my solution (overly complex) with respect to the classic one:
- I went into the direction of duplication removal, applying religiously one of the the remaining rules of Simple Design. I got to the point where I could configure my code just with the symbols I, V, X, L, C and their values instead of their subtractive combination. There were still some big assumptions to be addressed like the magic number 10 hardcoded inside the algorithm.
- Most of the classic solutions to kata are single procedures, which as such cannot respect the Open/Closed principle when a requirement changes or is added. Like for the Bowling kata, variation can make the short, terse 3-line solution explode. Any solution explodes when addressing the requirements that break its assumptions; in the case of the Bowling kata there are many more hardcoded values.
So yesterday I run a git init . on a new folder and I went into the direction of reuse...
Here's an interesting story, which may be fictional or real (doesn't matter for our purposes). Roman people were good at copying technology from the conquered populations. When they did conquer the Etruscan in Tuscany, they adopted their numeral system by changing the symbols.
So one additional requirement for the kata is to support the ancient Etruscan numeral system, which is identical to the Roman one except for the fact that the symbols are different:
1 => I 5 => ... 10 => ...
(there are other differences between the two systems that we purposefully ignore to keep the new requirement simple.)
Making subtractiveness configurable
When the actual Roman Empire existed, Roman numerals did not have a subtractive rule yet. The rule was introduced in medieval times to shorten the notation. So ancient Roman numerals were written like this:
4 => IIII 9 => VIIII 10 => XVIIII
One additional requirement to expand the kata is to support also this notation; in an object-oriented solution, this is usually accomplished by configuring the object graph differently. I got to this point:
new RomanNumeralsSystem([ // supports the classic numerals new SubtractiveRule(), new AdditiveRule() ]) new RomanNumeralsSystem([ // supports the additive only case new AdditiveRule() ])
I'm still not satisfied with the contract between the numeral system object and the rules, but this requirement (added after the first additive-subtractive case was solved) forced their extraction. The shape of the problem tells me only that there are some objects to be extracted from the monolithic procedure, but there may be many different ways to reach the same solution.
Even more variations
Some other variations that you can add to the problem to push for the extraction of objects and rules:
- what if the symbols follow a different scale, such as 1 => I, 5 => V, 20 => T instead of 1, 5, 10?
- What if symbols can be subtracted more than once? You could have numbers such as IXL instead of XXXIX.
- What about the other direction of the conversion? How easy it is to convert XXXIX into 39?
I have experimented with these new requirements but haven't got to a good implementation yet.
One thing I learned at the Agile Days this year is to held a little retrospective after each learning event, so I decided to apply that to kata execution too (7 Pomodoros should be well spent). Just spend some minutes reviewing `git log` and the problems encountered to see if there are weaknesses you need to explore (you need to commit frequently for this to work).
Some insights I got from my analysis:
- there is a tension between extracting objects early (which forces you to decompose the problem and has the benefit of having the feedback of unit tests for those objects) and waiting until you know more about the problem (which lets you refactor responsibilities between objects more easily; but that could be a scam.)
- You may be more precise, but my estimates at the Pomodoro level while doing a kata are easily of by a 2x factor.
- I do not took design choices but went down with one immediately. I should make sure to evaluate at least three different options before going down into implementing all of it, or set out the constraints at the start of a kata.
- Sometimes more refactoring does not make the code better. I should start timeboxing the improvement and throw away the code if it has not improved at the end of the allotted time, a suggestion I picked up at the course.