Code Monkey home page Code Monkey logo

gilded-rose's Introduction

Gilded Rose

This is a solution to the Gilded Rose refactoring kata

Creating the tests

Before touching the code is a good idea to make unit tests that satisfies all the specification of this kata. This is to make sure when changes happen the code still works as expected.

The initial test suite can be found here. It currently fails on two test cases because the tests expect the Conjured items to be handled. This feature needs to be implemented first.

When writing these tests it builds the confidence that the code can be changed as many times as we want since we can immediately verify if there are unwanted changes.

Analyzing the code

Now we have the tests, then we can start refactoring this code. The code is not straightforward. You have to read all the lines to understand what is going on.

To make sense of the code we can divide it into three parts:

1. Updating the quality or value of the item.

if (!items[i].name.equals("Aged Brie") && !items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
    if (items[i].quality > 0) {
        if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) {
            items[i].quality = items[i].quality - 1
        }
    }
} else {
    if (items[i].quality < 50) {
        items[i].quality = items[i].quality + 1

        if (items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
            if (items[i].sellIn < 11) {
                if (items[i].quality < 50) {
                    items[i].quality = items[i].quality + 1
                }
            }

            if (items[i].sellIn < 6) {
                if (items[i].quality < 50) {
                    items[i].quality = items[i].quality + 1
                }
            }
        }
    }
}

In the code above we can either increment or decrement the quality depending on the category. This is standard on the items and can be extracted to an extension function:

fun Item.decrementQuality() {
    if (quality > 0) {
        quality -= value
    }
}

fun Item.incrementQuality() {
    if (quality < 50) {
        quality++
    }
}

This way the code does not have to repeat itself in verifying the restrictions of the quality and incrementing the value. It can simply call this extension function.

2. Updating the sell in days

if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) {
   items[i].sellIn = items[i].sellIn - 1
}

This part is straightforward. The code decreases the sellIn value on each iteration except for Sulfuras which is a legendary item.

3. Updating the quality again when sell in date has passed

if (items[i].sellIn < 0) {
    if (!items[i].name.equals("Aged Brie")) {
        if (!items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
            if (items[i].quality > 0) {
                if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) {
                    items[i].quality = items[i].quality - 1
                }
            }
        } else {
            items[i].quality = items[i].quality - items[i].quality
        }
    } else {
        if (items[i].quality < 50) {
            items[i].quality = items[i].quality + 1
        }
    }
}

In this part, updates the quality similarly to step 1. The difference is that in this part the sellIn value is taken into consideration. If the date has passed then it either increments or decrements its value. The extension functions introduced earlier can be reused here.

Representing the categories

In the specification we can divide the categories into five:

  1. Regular
  2. Aged Brie
  3. Sulfuras
  4. Backstage Passes

Each category has a different way in updating its value at the end of the day. Previously we listed the parts of the code to understand it, and we can represent these parts into an interface:

interface Category {
    fun updateQuality(item: Item)
    fun updateSellIn(item: Item) { item.decrementSellIn() }
}

Then we can create a class for each category to implement this interface. In each class we can now write code that is solely responsible to its specification. This way it is easier to understand what each category does to its value. A default implementation on updating the sellIn value since it's similar on all the categories except one which is the Sulfuras category.

Refactoring the GildedRose class

We've created this classes but the GildedRose is still untouched. Now we can edit this class and be comfortable that we do not unintentionally remove a feature since we have our unit tests to protect us from that.

Putting it all together, the refactored updateQuality() will look like this:

    fun updateQuality() {
        items.forEach { item ->
            val category = getCategoryForItem(item)
            category.updateQuality(item)
            category.updateSellIn(item)
            if (item.sellIn < 0) {
                category.updateQuality(item)
            }
        }
    }

    private fun getCategoryForItem(item: Item): Category {
        return when {
            item.name == AGED_BRIE -> AgedBrieCategory
            item.name == BACKSTAGE_PASSES -> BackstageCategory
            item.name == SULFURAS -> SulfurasCategory
            else -> RegularCategory
        }
    }

As you can see the code is now easier to understand, and it makes much more sense now. The three steps we mentioned earlier are abstracted from the specific classes. You can read the code easily and know what it actually does without much effort.

Adding the Conjured category

Our tests are still failing because we have not implemented the Conjured category yet. It should be straightforward now since we already have the Category interface. We have to create a new class ConjuredCategory that implements this interface and attach it to our GildedRose class.

Apart from creating the class, we can also refactor our incrementQuality function to adjust the increment value:

fun Item.decrementQuality(value: Int = 1) {
    if (quality > 0) {
        quality -= value
    }

    /**
     * This case can happen when value is greater than 1
     */
    if (quality < 0) {
        quality = 0
    }
}

With the initial refactoring we made, we can do continuous refactoring to make the code tidy and not further introduce technical debt.

Text-Based Approval Testing

We can further verify that our refactored code works by testing the code by comparing the output of the code from a text file. We can use the sample from here. With few adjustments to make sure the Conjured category is being properly handled in the expected output.

The changes can be viewed in this commit.

Immutability

The problem with the current solution and the previous one was the mutability. In bigger projects, passing mutable objects is dangerous. This makes it hard for debugging and can lead to bugs that are hard to reproduce. This is also true when having projects that deals with concurrency. It can happen that the same array can be updated in multiple concurrent executions. This situation leads to unexpected mutations to the items and having unexpected results.

Although this kata is a simple project that is neither big in scale nor deals with concurrency. The solution presented is already enough and not over engineered. It's a solution that is maintainable and works.

There's another branch that solves this mutability issues. All objects are immutable and on every operation it creates a new object.

gilded-rose's People

Contributors

gumil avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.