Here I recognize that I need to apply one of two functions, depending on the input. This code works, but needs some cleaning up. First, as a stepping stone, I extract the lambdas into fields:
import java.util.function.Function;
public class FizzBuzzer implements Function<Integer, String> {
+ private final Function<Integer, String> replaceNumberWithStringRepresentation
+ = n -> Integer.toString(n);
+ private final Function<Integer, String> replaceNumberWithFizz
I generalized the single Function into a Stream of Functions, to which I apply the Map-Reduce pattern. I could have spelled out the Reduce part using something like .reduce("", (a, b) -> a + b), but I think Collectors.joining() is more expressive.
This doesn’t pass the test yet, since I return a stream of a single function. The fix is a little bit tricky, because I need to know whether any applicable replacer functions were found, and you can’t do that without terminating the stream. So I need to create a new stream using StreamSupport:
package remonsinnema.blog.fizzbuzz;
import java.util.Arrays;
import java.util.Collection;
+ import java.util.Iterator;
+ import java.util.Spliterators;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+ import java.util.stream.StreamSupport;
public class FizzBuzzer implements Function<Integer, String> {
Java comes with a whole bunch of functional interfaces, like Function and Predicate, that are easily combined with streams to solve a variety of problems.
The standard if → whiletransformation becomes if → stream in the functional world.
After only a couple of weeks of Judo practice, my son got bored. He complained that he wasn’t learning anything, because he kept doing the same thing over and over.
It’s not just young children that confuse learning and doing new things. For instance, how many software developers go through the trouble of deliberate practice by performing katas or attending dojos?
It may seem silly to repeat exercises that you’ve already done many times, but it’s not. It’s the only way to become a black belt in your field. And remember that mastery is one of the three intrinsic motivators (the others being autonomy and purpose).
Practicing means slowing down and moving focus from outcome to process. It’s best to use simple exercises that you can complete in a limited amount of time, so you can do the same exercise multiple times.
I’ve found that I virtually always learn something new when I practice. That’s not because I’ve forgotten how to solve the problem since last time, but because I’ve learned new things since then and thus see the world through new eyes.
For example, since Java 8 came out I’ve been trying to use the new stream classes to help move to a more functional style of programming. This has changed the way I look at old problems, like FizzBuzz.
Let’s see this in action. Of course, I start by adding a test:
+ package remonsinnema.blog.fizzbuzz;
+
+ import static org.junit.Assert.assertEquals;
+
+ import org.junit.Test;
+
+
+ public class WhenFizzingAndBuzzing {
+
+ private final FizzBuzz fizzbuzz = new FizzBuzz();
+
+ @Test
+ public void shouldReplaceWithFizzAndBuzz() {
+ assertEquals(“1”, “1”, fizzbuzz.get(1));
+ }
+
+ }
This test uses the When…Should form of unit testing that helps focus on behavior rather than implementation details. I let Eclipse generate the code required to make this compile:
+ package remonsinnema.blog.fizzbuzz;
+
+
+ public class FizzBuzz {
+
+ public String get(int i) {
+ return null;
+ }
+
+ }
The simplest code that makes the test pass is to fake it:
package remonsinnema.blog.fizzbuzz;
public class FizzBuzz {
public String get(int i) {
– return null;
+ return “1”;
}
}
Now that the test passes, it’s time for refactoring. I remove duplication from the test:
public class WhenFizzingAndBuzzing {
@Test
public void shouldReplaceWithFizzAndBuzz() {
– assertEquals(“1”, “1”, fizzbuzz.get(1));
+ assertFizzBuzz(“1”, 1);
+ }
+
+ private void assertFizzBuzz(String expected, int n) {
Next I add a test to force the real implementation:
public class WhenFizzingAndBuzzing {
@Test
public void shouldReplaceWithFizzAndBuzz() {
assertFizzBuzz(“1”, 1);
+ assertFizzBuzz(“2”, 2);
}
private void assertFizzBuzz(String expected, int n) {
package remonsinnema.blog.fizzbuzz;
public class FizzBuzz {
– public String get(int i) {
– return “1”;
+ public String get(int n) {
+ return Integer.toString(n);
}
}
OK, now let’s get real with a test for Fizz:
public class WhenFizzingAndBuzzing {
public void shouldReplaceWithFizzAndBuzz() {
assertFizzBuzz(“1”, 1);
assertFizzBuzz(“2”, 2);
+ assertFizzBuzz(“Fizz”, 3);
}
private void assertFizzBuzz(String expected, int n) {
package remonsinnema.blog.fizzbuzz;
public class FizzBuzz {
public String get(int n) {
+ if (n == 3) {
+ return “Fizz”;
+ }
return Integer.toString(n);
}
Similar for Buzz:
public class WhenFizzingAndBuzzing {
assertFizzBuzz(“Fizz”, 3);
+ assertFizzBuzz(“4”, 4);
+ assertFizzBuzz(“Buzz”, 5);
}
private void assertFizzBuzz(String expected, int n) {
public class FizzBuzz {
if (n == 3) {
return “Fizz”;
}
+ if (n == 5) {
+ return “Buzz”;
+ }
return Integer.toString(n);
}
Here I just copied and pasted the if statement to get it working quickly. We shouldn’t stop there, of course, but get rid of the dirty stuff. In this case, that’s duplication.
First, let’s update the code to make the duplication more apparent:
package remonsinnema.blog.fizzbuzz;
public class FizzBuzz {
public String get(int n) {
– if (n == 3) {
– return “Fizz”;
+ MultipleReplacer replacer = new MultipleReplacer(3, “Fizz”);
+ if (n == replacer.getValue()) {
+ return replacer.getText();
}
– if (n == 5) {
– return “Buzz”;
+ replacer = new MultipleReplacer(5, “Buzz”);
+ if (n == replacer.getValue()) {
+ return replacer.getText();
}
return Integer.toString(n);
}
+ package remonsinnema.blog.fizzbuzz;
+
+
+ public class MultipleReplacer {
+
+ private final int value;
+ private final String text;
+
+ public MultipleReplacer(int value, String text) {
+ this.value = value;
+ this.text = text;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ }
I just created a new value object to hold the two values that I had to change after the copy/paste.
Now that the duplication is clearer, it’s easy to remove:
package remonsinnema.blog.fizzbuzz;
+ import java.util.Arrays;
+ import java.util.Collection;
+
public class FizzBuzz {
+ private final Collection<MultipleReplacer> replacers = Arrays.asList(
+ new MultipleReplacer(3, “Fizz”), new MultipleReplacer(5, “Buzz”));
+
public String get(int n) {
– MultipleReplacer replacer = new MultipleReplacer(3, “Fizz”);
– if (n == replacer.getValue()) {
– return replacer.getText();
– }
– replacer = new MultipleReplacer(5, “Buzz”);
– if (n == replacer.getValue()) {
– return replacer.getText();
+ for (MultipleReplacer replacer : replacers) {
+ if (n == replacer.getValue()) {
+ return replacer.getText();
+ }
}
return Integer.toString(n);
}
I’m not done cleaning up, however. The current code suffers from feature envy, which I resolve by moving behavior into the value object:
package remonsinnema.blog.fizzbuzz;
import java.util.Arrays;
import java.util.Collection;
+ import java.util.Optional;
public class FizzBuzz {
public String get(int n) {
for (MultipleReplacer replacer : replacers) {
– if (n == replacer.getValue()) {
– return replacer.getText();
+ Optional<String> result = replacer.textFor(n);
+ if (result.isPresent()) {
+ return result.get();
}
}
return Integer.toString(n);
package remonsinnema.blog.fizzbuzz;
+ import java.util.Optional;
+
public class MultipleReplacer {
this.text = text;
}
– public int getValue() {
– return value;
– }
–
– public String getText() {
– return text;
+ public Optional<String> textFor(int n) {
+ if (n == value) {
+ return Optional.of(text);
+ }
+ return Optional.empty();
}
}
Now that I’m done refactoring, I can continue with multiples:
public class WhenFizzingAndBuzzing {
assertFizzBuzz(“Fizz”, 3);
assertFizzBuzz(“4”, 4);
assertFizzBuzz(“Buzz”, 5);
+ assertFizzBuzz(“Fizz”, 6);
}
private void assertFizzBuzz(String expected, int n) {
public class MultipleReplacer {
}
public Optional<String> textFor(int n) {
– if (n == value) {
+ if (n % value == 0) {
return Optional.of(text);
}
return Optional.empty();
The final test is for simultaneous “Fizz” and “Buzz”:
public class WhenFizzingAndBuzzing {
assertFizzBuzz(“4”, 4);
assertFizzBuzz(“Buzz”, 5);
assertFizzBuzz(“Fizz”, 6);
+ assertFizzBuzz(“7”, 7);
+ assertFizzBuzz(“8”, 8);
+ assertFizzBuzz(“Fizz”, 9);
+ assertFizzBuzz(“Buzz”, 10);
+ assertFizzBuzz(“11”, 11);
+ assertFizzBuzz(“Fizz”, 12);
+ assertFizzBuzz(“13”, 13);
+ assertFizzBuzz(“14”, 14);
+ assertFizzBuzz(“FizzBuzz”, 15);
}
private void assertFizzBuzz(String expected, int n) {
public class FizzBuzz {
new MultipleReplacer(3, “Fizz”), new MultipleReplacer(5, “Buzz”));
Now, what is responsible behavior in this context?
It’s many things. It’s delivering software that solves real needs, that works reliably, is secure, is a pleasure to use, etc. etc.
There is one constant in all these aspects: they change. Business needs evolve. New security threats emerge. New usability patterns come into fashion. New technology is introduced at breakneck speed.
The number one thing a software professional must do is to form an attitude of embracing change. We cope with change by writing programs that are easy to change.
Adaptability is not something we’ll explicitly see in the requirements; it’s silently assumed. We must nevertheless take our responsibility to bake it in.
Unfortunately, adaptability doesn’t find its way into our programs by accident. Writing programs that are easy to change is not easy but requires a considerable amount of effort and skill. The skill of a craftsman.
2. Hone Your Skills
How do we acquire the required skills to keep our programs adaptable?
We need to learn. And the more we learn, the more we’ll find that there’s always more to learn. That should make us humble.
How do we learn?
By reading/watching/listening, by practicing, and by doing. We need to read a lot and go to conferences to infuse our minds with fresh ideas. We need to practice to put such new ideas to the test in a safe environment. Finally, we need to incorporate those ideas into our daily practices to actually profit from them.
BTW, I don’t agree with the statement in the article that
Programmers cannot improve their skills by doing the same exercise repeatedly.
One part of mastering a skill is building muscle memory, and that’s what katas like Roman Numerals are for. Athletes and musicians understand that all too well.
Nowadays software development is mostly a team sport, because we’ve pushed our programs to the point where they’re too big to fail build alone. We are part of a larger community and the craftsmanship model emphasizes that.
There are both pros and cons to being part of a community. On the bright side, there are many people around us who share our interests and are willing to help us out, for instance in code retreats. The flip side is that we need to learn soft skills, like how to influence others or how to work in a team.
To make matters even more interesting, we’re actually simultaneously part of multiple communities: our immediate team, our industry (e.g. healthcare), and our community of interest (e.g. software security or REST), to name a few. We should participate in each, understanding that each of those communities will have their own culture.
It’s All About the Journey
Software craftsmanship is not about becoming a master and then resting on your laurels.
While we should aspire to master all aspects of software development, we can’t hope to actually achieve it. It’s more about the journey than the destination. And about the fun we can have along the way.
This left me with nothing for unit tests. So I installed the JavaScript tools from Eclipse. That gave me some JS support, but nothing for creating unit tests.
Some googling told me there is such a thing as JsUnit, the JS port of my beloved JUnit. Unfortunately it doesn’t seem to come with Eclipse support, even though this thread indicates it does (or did).
Maybe I’m just doing it wrong. I’d appreciate any hints in the comments.
Now that I’m all set up, it’s time to do a little exercise to get my feet wet. For this I picked the Roman Numerals kata.
I started out by following this JsTestDriver example. I created a new JavaScript project in Eclipse, added src/main/js and src/test/js folders, and created the JsTestDriver configuration file:
Next, I opened the JsTestDriver window using Window|Show View|Other|JavaScript|JsTestDriver and started the JsTestDriver server. I then opened the client in FireFox at http://127.0.0.1:42442/capture.
The next step was to create a new run configuration: Run|Run Configurations|JsTestDriver Test. I selected the project and the JsTestDriver configuration within the project, and checked Run on Every Save.
Now everything is set up to start the TDD cycle. First a test:
RomanNumeralsTest = TestCase("RomanNumeralsTest");
RomanNumeralsTest.prototype.testArabicToRoman
= function() {
var romanNumerals = new TestApp.RomanNumerals();
assertEquals("i", romanNumerals.arabicToRoman(1));
};
The cool thing about JsTestDriver is that it automatically runs all the tests every time you change something. This shortens the feedback cycle and keeps you in the flow. For Java, InfiniTest does the same.
The problem with my current tool chain is that support for renaming is extremely limited. I got Operation unavailable on the current selection. Select a JavaScript project, source folder, resource, or a JavaScript file, or a non-readonly type, var, function, parameter, local variable, or type variable.
Other refactorings do exist, like Extract Local Variable and Extract Method, but they mess up the formatting. They also give errors, but then work when trying again.
All in all I feel satisfied with the first steps I’ve taken on this journey. I’m a little worried about the stability of the tools. I also realize I have a more to learn about JavaScript prototypes.
Refactorings are standard alterations of the code that change its internal structure without changing its external behavior.
Now, if the Green and Refactor phases are each others opposite, then you might think that there are “opposite refactorings” as well. You would be right.
Robert Martin‘s transformations are standard alterations of the code that change its external behavior without changing its internal structure.
Automated Transformations?
Most of us use powerful IDEs to write our code. These IDEs support refactorings, which means that they can do the code alteration for you in a manner that is guaranteed to be safe.
So do we need something similar for transformations? I think not.
Some transformations are so simple in terms of the changes to code, that it wouldn’t actually save any effort to automate them. I don’t see a lot of room for improving the change from if to while, for instance.
Other transformations simply have an unspecified effect. For example, how would you automate the statement->statements transformation?
The crux is that refactorings keep the external behavior the same, and the tools depend on that to properly implement the refactorings. However, transformations don’t share that property.
Standardized Work
In the Specify/Transform/Refactor view of TDD, we write our programs by alternating between adding tests, applying transformations, and applying refactorings.
In other words, if we look at the evolution of our non-test code through a series of diffs, then each diff shows either a transformation or a refactoring.
There seems to be some confusion between Test-First Programming and Test-Driven Development (TDD).
This post explains that merely writing the tests before the code doesn’t necessarily make it TDD.
Similarities Between Test-First Programming and Test-Driven Development
It’s not hard to see why people would confuse the two, since they have many things in common.
My classification of tests distinguishes six dimensions: who, what, when, where, why, and how.
Test-First programming and Test-Driven Development score the same in five of those six dimensions: they are both automated (how) functional (what) programmer (who) tests at the unit level (where) written before the code (when).
The only difference is in why they are written.
Differences Between Test-First Programming and Test-Driven Development
Test-First Programming mandates that tests be written before the code, so that the code will always be testable. This is more efficient than having to change already written code to make it testable.
Test-First Programming doesn’t say anything about other activities in the development cycle, like requirements analysis and design.
This is a big difference with Test-Driven Development (TDD), since in TDD, the tests drive the design. Let’s take a detailed look at the TDD process of Red/Green/Refactor, to find out exactly how that differs from Test-First Programming.
Red
In the first TDD phase we write a test. Since there is no code yet to make the test pass, this test will fail.
Unit testing frameworks like JUnit will show the result in red to indicate failure.
In both Test-First Programming and Test-Driven Development, we use this phase to record a requirement as a test.
TDD, however, goes a step further: we also explicitly design the client API. Test-First Programming is silent on how and when we should do that.
Green
In the next phase, we write code to make the test pass. Unit testing frameworks show passing tests in green.
In Test-Driven Development, we always write the simplest possible code that makes the test pass. This allows us to keep our options open and evolve the design.
We may evolve our code using simple transformations to increase the complexity of the code enough to satisfy the requirements that are expressed in the tests.
Test-First Programming is silent on what sort of code you write in this phase and how you do it, as long as the test will pass.
Refactor
In the final TDD phase, the code is refactored to improve the design of the implementation.
This phase is completely absent in Test-First Programming.
Summary of Differences
So we’ve uncovered two differences that distinguish Test-First Programming from Test-Driven Development:
Test-Driven Development uses the Red phase to design the client API. Test-First Programming is silent on when and how you arrive at a good client API.
Test-Driven Development splits the coding phase into two compared to Test-First Programming. In the first sub-phase (Green), the focus is on meeting the requirements. In the second sub-phase (Refactor), the focus is on creating a good design.
I think there is a lot of value in the second point. Many developers focus too much on getting the requirements implemented and forget to clean up their code. The result is an accumulation of technical debt that will slow development down over time.
TDD also splits the design activity into two. First we design the external face of the code, i.e. the API. Then we design the internal organization of the code.
This is a useful distinction as well, because the heuristics you would use to tell a good API from a bad one are different from those for good internal design.
Try Before You Buy
All in all I think Test-Driven Development provides sufficient value over Test-First Programming to give it a try.
All new things are hard, however, so be sure to practice TDD before you start applying it in the wild.
Conferences are a great place to learn new things, but also to meet new people. New people can provide new ways of looking at things, which helps with learning as well.
You can either go to big and broad conferences, like Java One or the RSA conference, or you can attend a smaller, more focused event. Some of these smaller events may not be as well-known, but there are some real gems nonetheless.
Take XML Amsterdam, for example, a small conference here in the Netherlands with excellent international speakers and attendees (even some famous ones).
Attend Workshops
Learning is as much about doing as it is about hearing and watching. Some conferences may have hands-on sessions or labs, but they’re in the minority. So just going to conferences isn’t good enough.
A more practical variant are workshops. They are mostly organized by specific communities, like Java User Groups.
One particularly useful form for developers is the code retreat. Workshops are much more focused than conferences and still provide some of the same networking opportunities.
Get Formal Training
Lots of courses are being offered, many of them conveniently online. One great (and free) example is Cryptography from Coursera.
Some of these course lead to certifications. The world is sharply divided into those who think certifications are a must and those that feel they are evil. I’ll keep my opinion on this subject to myself for once 😉 but whatever you do, focus on the learning, not on the piece of paper.
Learn On The Job
There is a lot to be learned during regular work activities as well.
You can organize that a bit better by doing something like job rotation. Good forms of job rotation for developers are collective code ownership and swarming.
Test-Driven Development (TDD) is a scientific approach to software development that supports incremental design. I’ve found that, although very powerful, this approach takes some getting used to. Although the rules of TDD are simple, they’re not always easy:
Write a failing test
Write the simplest bit of code that makes it pass
Refactor the code to follow the rules of simple design
This is also called the Red-Green-Refactor cycle of TDD.
Writing a failing test isn’t always easy. Lots of TDD beginners write tests that are to big; TDD is all about taking baby steps. Likewise, writing the simplest bit of code to make the test pass is sometimes difficult. Many developers are trained to write generic code that can handle more than just the case at hand; they must unlearn these habits and truly focus on doing The Simplest Thing That Could Possibly Work.
The hardest part of TDD, however, is the final step. Some TDD novices skip it altogether, others have trouble evolving their design. Code that follows the rules of simple design
Passes all the tests
Contains no duplication
Clearly expresses the programmer’s intent
Minimizes code
The first is easy, since your xUnit framework will either give you Red or Green. But then the fun begins. Let’s walk through an example to see TDD in action.
In the Roman Numerals kata, we convert Arabic numbers (the one we use daily: 1, 2, 3, 4, 5, …) into their Roman equivalent: I, II, III, IV, V, … It’s a good kata, because it allows one to practice skills in a very concentrated area, as we’ll see.
So let’s get started. The first step is to write a failing test:
public class RomanNumeralsTest {
@Test
public void one() {
Assert.assertEquals("1", "I", RomanNumerals.arabicToRoman(1));
}
}
Note that this step really is not (only) about tests. It really is about designing your API from the client’s perspective. Think of something that you as a user of the API would like to see to solve your bigger problem. In this kata, the API is just a single method, so there is not much to design.
OK, we’re at Red, so let’s get to Green:
public class RomanNumerals {
public static String arabicToRoman(int arabic) {
return "I";
}
}
But that’s cheating! That’s not an algorithm to convert Arabic numerals into Roman! True, it’s not, but it isn’t cheating either. The rules of TDD state that we should write the simplest code that passes the test. It’s only cheating if you play by the old rules. You must unlearn what you have learned.
Now for the Refactor step. The test passes, there is no duplication, we express our intent (that currently is limited to convert 1 into I) clearly, and we have the absolute minimum number of classes and methods (both 1), so we’re done. Easy, right?
Now we move to the next TDD cycle. First Red:
public class RomanNumeralsTest {
@Test
public void oneTwo() {
Assert.assertEquals("1", "I", RomanNumerals.arabicToRoman(1));
Assert.assertEquals("2", "II", RomanNumerals.arabicToRoman(2));
}
}
No need to change our API. In fact we won’t have to change it again for the whole kata. So let’s move to Green:
public static String arabicToRoman(int arabic) {
if (arabic == 2) {
return "II";
}
return "I";
}
OK, we’re Green. Now let’s look at this code. It’s pretty obvious that if we continue down this path, we’ll end up with very, very bad code. There is no design at all, just a bunch of hacks. That’s why the Refactor step is essential.
We pass the tests, but how about duplication? There is duplication in the Arabic number passed in and the number of I’s the method returns. This may not be obvious to everyone because of the return in the middle of the method. Let’s get rid of it to better expose the duplication…
public static String arabicToRoman(int arabic) {
StringBuilder result = new StringBuilder();
if (arabic == 2) {
result.append("I");
}
result.append("I");
return result.toString();
}
…so that we can remove it:
public static String arabicToRoman(int arabic) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < arabic; i++) {
result.append("I");
}
return result.toString();
}
What we did here was generalize an if statement into a for (or while). This is one of a bunch of transformations one often uses in TDD. The net effect of a generalization like this is that we have discovered a rule based on similarities. This means that our code can now handle more cases than the ones we supplied as tests. In this specific case, we can now also convert 3 to III:
Now that we have removed duplication, let’s look at expressiveness and compactness. Looks OK to me, so let’s move on to the new case. We got 3 covered, so 4 is next:
@Test
public void four() {
Assert.assertEquals("4", "IV", RomanNumerals.arabicToRoman(4));
}
This fails as expected, because we generalized to far. We need an exception to our discovered rule:
public static String arabicToRoman(int arabic) {
StringBuilder result = new StringBuilder();
if (arabic == 4) {
return "IV";
}
for (int i = 0; i < arabic; i++) {
result.append("I");
}
return result.toString();
}
This uses return in the middle of a method again, which we discovered may hide duplication. So let’s not ever use that again. One alternative is to use an else statement. The other is to decrease the arabic parameter, so that the for loop never executes. We have no information right now on which to base our choice, so either will do:
public static String arabicToRoman(int arabic) {
StringBuilder result = new StringBuilder();
if (arabic == 4) {
result.append("IV");
} else {
for (int i = 0; i < arabic; i++) {
result.append("I");
}
}
return result.toString();
}
It’s hard to see duplication in here, and I think the code expresses our current intent and is small, so let’s move on to the next test:
@Test
public void five() {
Assert.assertEquals("5", "V", RomanNumerals.arabicToRoman(5));
}
Which we make pass with:
public static String arabicToRoman(int arabic) {
StringBuilder result = new StringBuilder();
if (arabic == 5) {
result.append("V");
} else if (arabic == 4) {
result.append("IV");
} else {
for (int i = 0; i < arabic; i++) {
result.append("I");
}
}
return result.toString();
}
This is turning into a mess. But there is no duplication apparent yet, and the code does sort of say what we mean. So let’s push our uneasy feelings aside for a little while and move on to the next test:
@Test
public void six() {
Assert.assertEquals("6", "VI", RomanNumerals.arabicToRoman(6));
}
Which passes with:
public static String arabicToRoman(int arabic) {
StringBuilder result = new StringBuilder();
if (arabic == 6) {
result.append("VI");
} else if (arabic == 5) {
result.append("V");
} else if (arabic == 4) {
result.append("IV");
} else {
for (int i = 0; i < arabic; i++) {
result.append("I");
}
}
return result.toString();
}
Hmm, uglier still, but at least some duplication is now becoming visible: VI is V followed by I, and we already have code to append those. So we could first add the V and then rely on the for loop to add the I:
public static String arabicToRoman(int arabic) {
StringBuilder result = new StringBuilder();
int remaining = arabic;
if (remaining >= 5) {
result.append("V");
remaining -= 5;
}
if (remaining == 4) {
result.append("IV");
remaining -= 4;
}
for (int i = 0; i < remaining; i++) {
result.append("I");
}
return result.toString();
}
Still not very clean, but better than before. And we can now also handle 7 and 8. So let’s move on to 9:
@Test
public void nineIsXPrefixedByI() {
Assert.assertEquals("9", "IX", RomanNumerals.arabicToRoman(9));
}
public static String arabicToRoman(int arabic) {
StringBuilder result = new StringBuilder();
int remaining = arabic;
if (remaining == 9) {
result.append("IX");
remaining -= 9;
}
if (remaining >= 5) {
result.append("V");
remaining -= 5;
}
if (remaining == 4) {
result.append("IV");
remaining -= 4;
}
for (int i = 0; i < remaining; i++) {
result.append("I");
}
return result.toString();
}
There’s definitely a pattern emerging. Two ifs use ==, and one uses >=, but the rest of the statements is the same. We can make them all completely identical by a slight generalization:
public static String arabicToRoman(int arabic) {
StringBuilder result = new StringBuilder();
int remaining = arabic;
if (remaining >= 9) {
result.append("IX");
remaining -= 9;
}
if (remaining >= 5) {
result.append("V");
remaining -= 5;
}
if (remaining >= 4) {
result.append("IV");
remaining -= 4;
}
for (int i = 0; i < remaining; i++) {
result.append("I");
}
return result.toString();
}
Now we can extract the duplication:
public static String arabicToRoman(int arabic) {
StringBuilder result = new StringBuilder();
int remaining = arabic;
remaining = appendRomanNumerals(remaining, 9, "IX", result);
remaining = appendRomanNumerals(remaining, 5, "V", result);
remaining = appendRomanNumerals(remaining, 4, "IV", result);
for (int i = 0; i < remaining; i++) {
result.append("I");
}
return result.toString();
}
private static int appendRomanNumerals(int arabic, int value, String romanDigits, StringBuilder builder) {
int result = arabic;
if (result >= value) {
builder.append(romanDigits);
result -= value;
}
return result;
}
There is still duplication is the enumeration of calls to appendRomanNumerals. We can turn that into a loop:
private static final int[] VALUES = { 9, 5, 4 };
private static final String[] SYMBOLS = { "IX", "V", "IV" };
public static String arabicToRoman(int arabic) {
StringBuilder result = new StringBuilder();
int remaining = arabic;
for (int i = 0; i < VALUES.length; i++) {
remaining = appendRomanNumerals(remaining, VALUES[i], SYMBOLS[i], result);
}
for (int i = 0; i < remaining; i++) {
result.append("I");
}
return result.toString();
}
Now that we look at the code this way, it seems that the for loop does something similar to what appendRomanNumerals does. The only difference is that the loop does it multiple times, while the method does it only once. We can generalize the method and rewrite the loop to make this duplication more visible:
public static String arabicToRoman(int arabic) {
StringBuilder result = new StringBuilder();
int remaining = arabic;
for (int i = 0; i < VALUES.length; i++) {
remaining = appendRomanNumerals(remaining, VALUES[i], SYMBOLS[i], result);
}
while (remaining >= 1) {
result.append("I");
remaining -= 1;
}
return result.toString();
}
private static int appendRomanNumerals(int arabic, int value, String romanDigits, StringBuilder builder) {
int result = arabic;
while (result >= value) {
builder.append(romanDigits);
result -= value;
}
return result;
}
This makes it trivial to eliminate it:
private static final int[] VALUES = { 9, 5, 4, 1 };
private static final String[] SYMBOLS = { "IX", "V", "IV", "I" };
public static String arabicToRoman(int arabic) {
StringBuilder result = new StringBuilder();
int remaining = arabic;
for (int i = 0; i < VALUES.length; i++) {
remaining = appendRomanNumerals(remaining, VALUES[i], SYMBOLS[i], result);
}
return result.toString();
}
Now we have discovered our algorithm and new cases can be handled by just adding to the arrays:
There is still some duplication in the arrays, but one can wonder whether eliminating it would actually improve the code, so we’re just going to leave it as it is. And then there is some duplication in the fact that we have two arrays with corresponding elements. Moving to one array would necessitate the introduction of a new class to hold the value/symbol combination and I don’t think that that’s worth it.
So that concludes our tour of TDD using the Roman Numerals kata. What I really like about this kata is that it is very focused. There is hardly any API design, so coming up with tests is really easy. And the solution isn’t all that complicated either; you only need if and while. This kata really helps you hone your skills of detecting and eliminating duplication, which is a fundamental skill to have when evolving your designs.
Have fun practicing!
Update: Here is a screencast of a better version of the kata:
You must be logged in to post a comment.