Lesson Learned From Gilded Rose Kata
Code kata practice can make you a stronger developer. Check out one developer's experience.
Join the DZone community and get the full member experience.
Join For FreeI’d like to share some of my thoughts about my approach to solve the Gilded Rose Refactoring Kata by Emily Bache. If you don’t know this kata, read the description for a better understanding. I have published my whole solution on GitHub . I tried to make a commit after every step, so you can keep track of my steps in the log of Git. The chosen programming language is Java.
Solving Gilded Rose Step-By-Step
Let’s have a look at what I have done step-by-step.
Before adding the new feature, I wanted to refactor the given code base. Therefore, I started writing tests till I had a 100% line and branch coverage. During writing the tests, I was having the idea that the calculation of the quality is dependent on the name of the item. Hence, the idea arose to use something similar to the Strategy Pattern. When I reached for 100% coverage, I tried to start with the implementation for the first strategy (“Aged Brie”). But I was unsure what my limit values were for this first strategy. My problem was that I hadn’t tested for the limit values. So my first lesson learned was that 100% line or branch coverage doesn’t mean all test cases are covered. So I added tests for the limit values and finished implementing the “Aged Brie” strategy, adding it to the original updateQualtity method (see below code snippet) and ran the tests. All tests were green.
ItemStrategy itemStrategy = new ItemStrategy();
...
for (int i = 0; i < items.length; i++) {
if("Aged Brie".equals(items[i].name)) {
items[i] = itemStrategy.updateQualityForAgedBrieItem(items[i]);
continue;
}
// original code follows
}
These cycle I repeated four times: Find missing test cases (mostly for limit values); add new tests for these cases; implement a further strategy; add this new strategy to the originalupdateQualtiy method and ran the tests. If the tests are green, the next cycle with a new strategy begins. In the end the extended updatedQuality method looked like the following code snippet.
ItemStrategy itemStrategy = new ItemStrategy();
...
for (int i = 0; i < items.length; i++) {
if("Aged Brie".equals(items[i].name)) {
items[i] = itemStrategy.updateQualityForAgedBrieItem(items[i]);
continue;
} else if ("Sulfuras, Hand of Ragnaros".equals(items[i].name)) {
items[i] = itemStrategy.updateQualityForSulfurasItem(items[i]);
continue;
} else if("Backstage passes to a TAFKAL80ETC concert".equals(items[i].name)) {
items[i] = itemStrategy.updateQualityForBackstagePassItem(items[i]);
continue;
} else {
items[i] = itemStrategy.updateQualityForNormalItem(items[i]);
continue;
}
// commented out original code
}
My second lesson learned was “Refactoring needs time” and the refactoring wasn’t finished. The next steps were cleaning up unnecessary code and refactoring the strategy implementations like replacing if-else constructs with ternary operators and extracting if-conditions to private methods.
After that I implemented the new feature “conjured item” following the above describe workflow. After this step I could say “Ready”, but I was unhappy with the if-else if-else chain. Therefore, I decided to extract each strategy implementation to its own class (following the “classic” strategy pattern). That helps to replace the if-else if-else chain by an itemStrategyMap. So the next lesson learned was “The status ‘Ready’ depends on the definition”.
The last step was doing clean up and choosing better names for the interface and its method.
static Map<String, ItemStrategy> itemStrategyMap = new HashMap<>();
static {
itemStrategyMap.put("Aged Brie", new AgedBrieItemStrategy());
itemStrategyMap.put("Sulfuras, Hand of Ragnaros", new SulfurasItemStrategy());
itemStrategyMap.put("Backstage passes to a TAFKAL80ETC concert", new BackstagePassItemStrategy());
itemStrategyMap.put("Conjured", new ConjuredItemStrategy());
}
public void updateQuality() {
for (int i = 0; i < items.length; i++) {
ItemStrategy itemStrategy = itemStrategyMap.getOrDefault(items[i].name, new NormalItemStrategy());
items[i] = itemStrategy.updateItem(items[i]);
}
}
Let’s summarize the lessons learned:
100% line or branch coverage doesn’t mean all test cases are covered.
Refactoring needs time.
The status ‘Ready’ depends on the definition.
These insights aren’t really new for me. I can often observe these insights in my daily work. Nevertheless, it was good to have these insights again, following the rule “learning through repetition” ☺
What I Forgot
I stopped after that step. Thinking about it some days later, I have realized that there could have been more improvements. For example, the tests from GildedRoseTest class could be extracted to separate test classes regarding the specific strategy classes.
Published at DZone with permission of Sandra Parsick, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments