Overview Refactoring legacy code often feels like walking through a minefield. You change one line of an `if` statement, and suddenly a seemingly unrelated feature breaks. The Gilded Rose Kata, originally created by Terry Hughes, is the industry-standard exercise for practicing how to handle these "spaghetti code" scenarios. This tutorial demonstrates a systematic 5-step framework to transform messy, nested conditional logic into a clean, extensible class hierarchy using Python. Prerequisites To follow this guide, you should be comfortable with basic Python syntax, including loops and conditional statements. Familiarity with Object-Oriented Programming (OOP) concepts like classes and inheritance is helpful, along with a basic understanding of how to run tests using pytest. Key Libraries & Tools * **Python**: The primary programming language used for the implementation. * **pytest**: A robust testing framework used to build our safety net. * **GitHub Copilot**: An AI pair programmer used to accelerate boilerplate generation and test writing. * **Hypothesis**: A library for property-based testing (recommended for advanced edge-case detection). Step 1 & 2: Analysis and Goal Setting Before touching the keyboard, you must understand the business logic. In the Gilded Rose, items have a `sell_in` value (days remaining) and a `quality` value. Most items degrade over time, but "Aged Brie" improves, and "Sulfuras" is a legendary item that never changes. Our specific goal is to add a new "Conjured" item type. The measure of success is simple: after refactoring, adding this new type should require minimal changes to the core engine. Step 3: Creating the Safety Net You cannot safely refactor without tests. If you don't have a safety net, you're just guessing. Start by writing pytest functions for every requirement. ```python import pytest from gilded_rose import Item, GildedRose def test_item_quality_decreases_over_time(): items = [Item("Standard Item", 10, 20)] gilded_rose = GildedRose(items) gilded_rose.update_quality() assert items[0].quality == 19 assert items[0].sell_in == 9 def test_quality_never_negative(): items = [Item("Standard Item", 10, 0)] gilded_rose = GildedRose(items) gilded_rose.update_quality() assert items[0].quality == 0 ``` Step 4: Step-by-Step Refactoring Refactor in small, verifiable increments. I start by extracting the logic into a separate function to reduce indentation and then move hard-coded strings into constants. Inverting Logic to Untangle Nesting One of the most powerful moves is inverting `if not` statements. By focusing on what an item **is** rather than what it **isn't**, we can flatten the logic into a clean `if/elif` chain. Implementing the Strategy Pattern Once the logic is flat, we replace the conditionals with an inheritance hierarchy. Each item type gets its own updater class. ```python from typing import Protocol class ItemUpdater(Protocol): def update_quality(self, item: Item) -> None: ... def update_sellin(self, item: Item) -> None: ... class DefaultUpdater: def update_sellin(self, item: Item): item.sell_in -= 1 def update_quality(self, item: Item): item.quality -= 1 if item.sell_in < 0: item.quality -= 1 ``` We then map these to a dictionary for easy lookup: ```python UPDATER_MAP = { "Aged Brie": AgedBrieUpdater(), "Backstage passes": BackstagePassUpdater(), "Sulfuras": SulfurasUpdater(), } ``` Step 5: Measuring Success With our new structure, adding the "Conjured" item—our original goal—becomes trivial. We simply create a new class and add it to the map. ```python class ConjuredUpdater(DefaultUpdater): def update_quality(self, item: Item): item.quality -= 2 if item.sell_in < 0: item.quality -= 2 ``` Syntax Notes * **Protocols**: We use `typing.Protocol` for structural subtyping, which defines an interface without requiring explicit inheritance in the base implementation. * **Type Annotations**: Adding types to the legacy `Item` class immediately improves IDE support and helps catch bugs during the refactor. * **Max/Min Functions**: Use `max(0, quality)` and `min(50, quality)` to keep values within bounds instead of writing multiple `if` checks. Tips & Gotchas * **Commit Often**: Commit every time your tests pass. If you break something three steps later, you can revert easily. * **Avoid AI Blindness**: GitHub Copilot is great at generating tests, but it often gets the Gilded Rose's weird logic wrong. Always verify the expected values manually. * **Don't Change the Item Class**: In the original Kata, you aren't allowed to touch the `Item` class. Use a wrapper or an updater class to work around this constraint.
Terry Hughes
People
Sep 2023 • 1 videos
High activity month for Terry Hughes. ArjanCodes among the most active voices, with 1 videos across 1 sources.
Sep 2023
- Sep 1, 2023