## DEV Community

Lomig

Posted on • Updated on

# A Walk Through the Gilded Rose Kata — Pt 2: Duplication is your friend (at first)

So, we left our kata at the end of the last post with some tests, but still no clue about how to handle the entanglement in the gross conditional that is at the core of the GildedRose app.

If you tried this kata yourself, or if you try to relate with some difficult pieces of code you encountered, you perhaps tried to understand a bit of what happens, and tiny fragment by tiny fragment, patiently untangle the code.

As I said, I will not; I cannot. It is a conditional! Yuck!
What I understand and like, though, are guard clauses.
Guard clauses are simple, they are nice, and most importantly to me, they are flat.

# A guard clause?

For people unfamiliar with the concept, a guard clause is a pattern that would require to leave a method very early and return easy results, or special cases results, for the method to focus on its main algorithm.

Everything is better and clearer with an example! Imagine a method that would divide an integer by another one.

``````def divide(dividend:, divisor:)
dividend.to_f / divisor
end
``````

That is splendid, but if `divisor` is `0`, there will be an issue. We want to return the string `"error"` when a user tries such a calculus.

``````# Conditional
def divide(dividend:, divisor:)
if !divisor.zero?
dividend.to_f / divisor
else
"error"
end
end

# Guard clause
def divide(dividend:, divisor:)
return "error" if divisor.zero?

dividend.to_f / divisor
end
``````

It works because `return` stops the execution of the method, making any `else` useless.

But how can we apply this concept to my messy method at hand?

# Excluding a first special case from the execution

Instead of trying to untangle what I have, I will exclude something. Anything. Let us take a look at the code:

``````def update_quality()
@items.each do |item|
if item.name != "Aged Brie" and item.name != "Backstage passes to a TAFKAL80ETC concert"
# <Moar code here!>
end
end
end
``````

There seem to be a case where `item.name` equals `Aged Brie`; I decide (at random, any other would work, it was just the first to appear here) it will be my special case.

I just want to return something in that case. What do I want to return? I don't know yet, but is it a problem? Unit Tests have my back in any case!

⚠️ In the Guard Clause example above, I used `return` to stop the execution of the method. Here, I will want to stop the execution of a block. Unlike some other languages, Ruby is nice enough to provide a substitute for `return` in blocks: `next`.

``````def aged_brie_update(item)
# Here will be what we want to do with our Aged Brie!
end

def update_quality()
@items.each do |item|
next aged_brie_update(item) if item.name == "Aged Brie"

if item.name != "Aged Brie" and item.name != "Backstage passes to a TAFKAL80ETC concert"
# <Moar code here!>
end
end
end
``````

Again, you can see I do not try to untangle the conditional. If the item is the `Aged Brie`, I will be able to deal with it outside of the conditional. If it is not, the conditional will take over. That way, the only tests that should fail are the one related to the `Aged Brie`: obviously, we do not do anything with it 😊

So, test by test, I work towards green. I do not really care about special refactoring, even though we can argue I have my coding style. It seems simple enough, far away from the problems of our `#update_quality` method.

What do we want?

• Each time, it ages;
• Each time, it gains 1 quality;
• If `item.sell_in` is overdue, each time it gains an extra quality;
• It cannot go above 50 quality.
``````def aged_brie_update(item)
item.sell_in -= 1

item.quality +=1
item.quality +=1 if item.sell_in < 0
item.quality = 50 if item.quality > 50
end
``````

My tests being green again, I can take for granted that I did not break anything.

You may think that I made the execution path even more complicated, and the `GildedRose` class even longer, but bear with me: do not forget I am a stupid man, and I would do anything to punch an `else` in the guts.

# Repeat...

I take my code:

``````def aged_brie_update(item)
item.sell_in -= 1

item.quality +=1
item.quality +=1 if item.sell_in < 0
item.quality = 50 if item.quality > 50
end

def update_quality()
@items.each do |item|
next aged_brie_update(item) if item.name == "Aged Brie"

if item.name != "Aged Brie" and item.name != "Backstage passes to a TAFKAL80ETC concert"
# <Moar code here!>
end
end
end
``````

I extract a special case:

``````def aged_brie_update(item)
item.sell_in -= 1

item.quality +=1
item.quality +=1 if item.sell_in < 0
item.quality = 50 if item.quality > 50
end

def backstage_update(item)
#What should we do with the backstage pass?
end

def update_quality()
@items.each do |item|
next aged_brie_update(item) if item.name == "Aged Brie"
next backstage_update(item) if item.name == "Backstage passes to a TAFKAL80ETC concert"

if item.name != "Aged Brie" and item.name != "Backstage passes to a TAFKAL80ETC concert"
# <Moar code here!>
end
end
end
``````

My tests are red for the Backstage passes, and I make them green:

``````def aged_brie_update(item)
item.sell_in -= 1

item.quality +=1
item.quality +=1 if item.sell_in < 0
item.quality = 50 if item.quality > 50
end

def backstage_update(item)
item.sell_in -= 1
return item.quality = 0 if item.sell_in < 0

item.quality += 1
item.quality += 1 if item.sell_in < 10
item.quality += 1 if item.sell_in < 5
item.quality = 50 if item.quality > 50
end

def update_quality()
@items.each do |item|
next aged_brie_update(item) if item.name == "Aged Brie"
next backstage_update(item) if item.name == "Backstage passes to a TAFKAL80ETC concert"

if item.name != "Aged Brie" and item.name != "Backstage passes to a TAFKAL80ETC concert"
# <Moar code here!>
end
end
end
``````

# And rinse!

Extraction by extraction, guard clause by guard clause, I work toward a state where I cannot find any special case anymore.

At this stage, my code looks like this (notice the Sulfuras method that is empty 😀 — as soon as I extracted this special case, the tests were already green!):

``````def aged_brie_update(item)
item.sell_in -= 1

item.quality +=1
item.quality +=1 if item.sell_in < 0
item.quality = 50 if item.quality > 50
end

def backstage_update(item)
item.sell_in -= 1
return item.quality = 0 if item.sell_in < 0

item.quality += 1
item.quality += 1 if item.sell_in < 10
item.quality += 1 if item.sell_in < 5
item.quality = 50 if item.quality > 50
end

def sulfuras_update(item)
end

def update_quality()
@items.each do |item|
next aged_brie_update(item) if item.name == "Aged Brie"
next backstage_update(item) if item.name == "Backstage passes to a TAFKAL80ETC concert"
next sulfuras_update(item)  if item.name == "Sulfuras, Hand of Ragnaros"

if item.name != "Aged Brie" and item.name != "Backstage passes to a TAFKAL80ETC concert"
# <Moar code here!>
end
end
end
``````

As I do not have any special case anymore, what does the conditional do?
Well, in fact we did not deal with all the special cases: We also need to deal with the "not special case", when my product is neither cheese, concert ticket, nor Sulfuras.

I think this is something humans tend to forget: whenever we talk about X special cases, in reality we speak about X + 1 special cases, counting "not special".

# Final extraction

We remove the now useless conditional at last, then make the tests for any "Normal item" green, and this is the result — the complete code, this time:

``````class GildedRose

def initialize(items)
@items = items
end

def aged_brie_update(item)
item.sell_in -= 1

item.quality +=1
item.quality +=1 if item.sell_in < 0
item.quality = 50 if item.quality > 50
end

def backstage_update(item)
item.sell_in -= 1
return item.quality = 0 if item.sell_in < 0

item.quality += 1
item.quality += 1 if item.sell_in < 10
item.quality += 1 if item.sell_in < 5
item.quality = 50 if item.quality > 50
end

def sulfuras_update(item)
end

def common_update(item)
item.sell_in -= 1

item.quality -= 1
item.quality -= 1 if item.sell_in < 0
item.quality = 0 if item.quality < 0
end

def update_quality()
@items.each do |item|
next aged_brie_update(item) if item.name == "Aged Brie"
next backstage_update(item) if item.name == "Backstage passes to a TAFKAL80ETC concert"
next sulfuras_update(item)  if item.name == "Sulfuras, Hand of Ragnaros"

common_update(item)
end
end
end

class Item
attr_accessor :name, :sell_in, :quality

def initialize(name, sell_in, quality)
@name = name
@sell_in = sell_in
@quality = quality
end

def to_s()
"#{@name}, #{@sell_in}, #{@quality}"
end
end
``````

# Do I see the light?

Well, do not worry, I have more tricks in my bag. This being said, we could argue that already it looks better, and it would be easier to add our `"Conjured items"` new product.

Before ending this second part (there will be more), we can at least remove our guard clauses here. The signal is this pattern:

``````def update_quality()
@items.each do |item|
case item.name
when "Aged Brie"
aged_brie_update(item)
when "Backstage passes to a TAFKAL80ETC concert"
backstage_update(item)
when "Sulfuras, Hand of Ragnaros"
sulfuras_update(item)
else
common_update(item)
end
end
end
``````

And I will let you ponder this change (I said I hated conditionals, right?) until part 3!

DEV Community

## The AI Brief

### AI generated git commit messages

Minimize the struggle of remembering what you just coded. AI-generated commits make it easier to manage projects and keep track of changes. The Nutlope/aicommits project demonstrates how AI can improve commit messages.

### I open sourced an AI that creates any UI in seconds

Make AI-generated user interfaces a breeze. This open-source project harnesses the power of generative AI technologies like chatGPT to create versatile, quick, and intuitive UI components.

### Use AI to commit like a PRO in 1 second

Upgrade your commit message game with AI. Boost your productivity by using ChatGPT to generate commit messages and avoid context switching. OpenCommit is an open-source library that helps you achieve this easily.

### Build your own ChatGPT starter kit

Train AI models on custom data for improved domain-specific knowledge. Combine the power of WebView technologies and this starter kit to train your ChatGPT model on specific websites, allowing for better-optimized outcomes.