Demystifying Gilded Rose is a very popular refactoring exercise that is intended for practicing your test case writing and refactoring skills. It represents the dreaded situation we have to face as software engineers when we have to work on a legacy codebase and add a new feature to it. Soon after implementing the feature, we discover that some part of our system starts to malfunction even though we have not touched it. Refactoring becomes a necessity then. And refactoring without writing unit tests is a pain in the neck because you do not know what side effects you are going to introduce that may eventually make the whole system grind to a halt.
Gilded Rose sells some finest goods belonging to the following categories:
You are given the task of implementing a new feature to an existing legacy system so that they can sell a new category of Conjured items. But the system is nothing short of a complete mess of unmaintainable spaghetti code with a bunch of if-else statements. Furthermore, there are no test cases!
Please take your time and read the Gilded Rose Requirements Specification here.
Before you refactor a codebase, you must make sure:
When you refactor your codebase, you must make sure:
docstrings for code documentation. Docstrings are not separate from the actual code, they are a part of the code itself.Let’s start solving the kata now.
Guess what? You are given a system that does not have test cases and is very tightly coupled.
First thing first, let’s write some unit tests for all the categories of items that Gilded Rose will sell. (I am assuming that you have already read the requirement specification of Gilded Rose)
There are several libraries in Python for writing unit tests. The most popular ones are being unittest and pytest.
In this tutorial, I will be using the built-in unittest library.
We want to update the quality of an item multiple times. So it’s better to have a bulk updater. Let’s implement a static method for that. Here is the complete test case suite for the items sold by Gilded Rose.
import unittest
from gilded_rose import Gilde
If you look at the current state of Gilded Rose’s codebase, especially the update_quality method of the GildedRose class, you’ll see how messy it is.
You cannot implement a new feature to this codebase without refactoring it first. So, how to refactor this chaos without affecting the behavior of the system?
You may be tempted to make the Item class a subclass of ABC – the abstract base class in Python and have a update_quality abstract method. Any class that inherits from the Item class will require to implement the update_quality method of their own.
from abc import ABC, abstractmethod
But there is something terribly wrong. Although you have successfully separated items and retained their behavior, you have failed to comply with the stated requirements not to alter the Item class: However, do not alter the Item class or Items property as those belong to the goblin in the corner who will insta-rage and one-shot you as he doesn't believe in shared code ownership (you can make the UpdateQuality method and Items property static if you like, we'll cover for you). You were not given access to the consumer code, which only instantiates objects of type Item, so the system will fail altogether.
Let’s implement some free functions and let them update the items. First, refactor the update_quality method of the GildedRose class.
class GildedRose:
This is cleaner compared to what we have started with. We have removed all the nested `if conditionals. Let’s define constants for MAX and MIN quality.
MAX_QUALITY = 50 MIN_QUALITY = 0
In general, all items have the following properties as specified in the requirement specification:
But there is a special condition:
Let’s convert all these requirements into code.
def normal_item_updater(normal_item: Item):
There is an interesting fact about the Aged Brie items.
If we read the requirement specification, we will see that we don’t even need an updater for the Sulfuras items.
Ahh! Good riddance!
This is a little bit tricky to implement. Let’s revisit the special condition of Backstage passes items.
Now, let’s convert this into code:
We are done refactoring the codebase. It’s time to implement a new feature for the Conjured items.
We have added unit tests, refactored the legacy codebase and also implemented the new feature. Let’s run the tests to verify if we have been able to retain the behavior of the systems.
Hooray! All the five test cases passed!