Skip to content

javascript

My take on the Gilded Rose kata

The Gilded Rose Kata by Emily Bache is a staple in refactoring exercises. It offers a deceptively simple problem: refactor an existing codebase while preserving its behavior. I recently worked through the TypeScript version of the kata, and this post documents the transformation from a legacy mess into clean, testable code—with examples along the way.

But before diving into the code, I should mention: this was my very first encounter with TypeScript. I had never written a single line in the language before this exercise. That added an extra layer of learning—on top of refactoring legacy code, I was also picking up TypeScript’s type system, syntax, and tooling from scratch.


🧪 Development Workflow

Pre-Commit Hooks

pre-commit.com is a framework for managing and maintaining multi-language pre-commit hooks. It allows you to define a set of checks (such as code formatting, linting, or security scans) that automatically run before every commit, helping ensure code quality and consistency across a team. Hooks are easily configured in a .pre-commit-config.yaml file and can be reused from popular repositories or custom scripts. It integrates seamlessly with Git and supports many languages and tools out of the box.

I added eslint and gitlint:

- repo: https://github.com/pre-commit/mirrors-eslint
  hooks:
    - id: eslint

  - repo: https://github.com/jorisroovers/gitlint
    hooks:
      - id: gitlint

GitHub Actions

GitHub Actions was used to automate the testing workflow, ensuring that every push runs the full test suite. This provides immediate feedback when changes break functionality, which was especially important while refactoring the legacy Gilded Rose code. The setup installs dependencies with npm, runs tests with yarn, and ensures consistent results across different environments—helping maintain code quality and giving confidence to refactor freely while learning TypeScript.

name: Build

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [12.x]

    steps:
      - uses: actions/checkout@v2
      - name: Node.js
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm install -g yarn
        working-directory: ./TypeScript
      - name: yarn install, compile and test
        run: |
          yarn
          yarn compile
          yarn test
        working-directory: ./TypeScript

🔍 Starting Point: Legacy Logic

Originally, everything was handled in a massive updateQuality() function using nested if statements like this:

if (item.name !== 'Aged Brie' && item.name !== 'Backstage passes') {
    if (item.quality > 0) {
        item.quality--;
    }
} else {
    if (item.quality < 50) {
        item.quality++;
    }
}

The function mixed different concerns and was painful to extend.


🧪 Building Safety Nets

Golden master tests are a technique used to protect legacy code during refactoring by capturing the current behavior of the system and comparing it against future runs. In this project, I recorded the output of the original updateQuality() function across many item variations. As changes were made to clean up and restructure the logic, the tests ensured that the external behavior remained identical. This approach was especially useful when the codebase was poorly understood or untested, offering a reliable safety net while improving internal structure.

expect(goldenMasterOutput).toEqual(currentOutput);

🧹 Refactoring: Toward Structure and Simplicity

1. Extracting Logic

I moved logic to a separate method:

private doUpdateQuality(item: Item) {
    // clean, focused logic
}

This isolated the business rules from boilerplate iteration.

2. Replacing Conditionals with a switch

Using a switch statement instead of multiple if/else if blocks makes the code cleaner, more readable, and easier to maintain—especially when checking a single variable (like item.name) against several known values. It clearly separates each case, making it easier to scan and reason about the logic. In the Gilded Rose project, switching to switch also made it easier to later refactor into specialized handlers or classes for each item type, as each case represented a clear and distinct behavior to isolate.

switch (item.name) {
    case 'Aged Brie':
        this.updateBrie(item);
        break;
    case 'Sulfuras':
        break; // no-op
    case 'Backstage passes':
        this.updateBackstage(item);
        break;
    default:
        this.updateNormal(item);
}

This increased clarity and prepared the ground for polymorphism or factory patterns later.


🛠 Polishing the Code

Constants and Math Utilities

Instead of magic strings and numbers, I introduced constants:

const MAX_QUALITY = 50;
const MIN_QUALITY = 0;

I replaced verbose checks with:

item.quality = Math.min(MAX_QUALITY, item.quality + 1);

Factory Pattern

The factory pattern is a design pattern that creates objects without exposing the exact class or construction logic to the code that uses them. Instead of instantiating classes directly with new, a factory function or class decides which subclass to return based on input—like item names in the Gilded Rose kata. This makes it easy to add new behaviors (e.g., “Conjured” items) without changing existing logic, supporting the Open/Closed Principle and keeping the code modular and easier to test or extend.

switch (true) {
    case /^Conjured/.test(item.name):
        return new ConjuredItem(item);
    case item.name === 'Sulfuras':
        return new SulfurasItem(item);
    // ...
}

🌟 Feature Additions

With structure in place, adding Conjured Items was straightforward:

class ConjuredItem extends ItemUpdater {
    update() {
        this.decreaseQuality(2);
        this.decreaseSellIn();
    }
}

A corresponding test was added to confirm behavior.


🎯 Conclusion

The journey from legacy to clean architecture was iterative and rewarding. Key takeaways:

  • Set up CI and hooks early to enforce consistency.
  • Use golden master tests for safety.
  • Start small with extractions and switch statements.
  • Add structure gradually—factories, constants, classes.
  • With a clean base, adding features like “Conjured” is trivial.

All this while learning TypeScript for the first time!

You can explore the full codebase and history here:
📦 Gilded Rose Refactoring Kata — TypeScript branch

Curious to try it yourself, also in other languages?
Fork Emily Bache’s repo here: GildedRose-Refactoring-Kata on GitHub

horse chest piece on chess board

OMG Unicorns!

The Daily WTFOp de disclaimer-pagina staat een aardigheidje. Je moet zelf maar eens zoeken.

Ik heb dit idee gehaald bij TheDailyWTF. Als je het zelf ook op een WordPress site wil hebben, dan doe je het volgende:

  • Wijzig de editor van Visual naar Text
  • Zet dit bovenaan in de pagina:
    <script type="text/javascript" src="http://www.cornify.com/js/cornify.js"></script>
  • Zet dit aan het begin van een blok tekst:
    <span onclick="cornify_add();return false;" title="click me!">
    en dit aan het einde:
    </span>
  • Bewaar de pagina.

LET OP! WordPress heeft de vervelende gewoonte om <span>-tags zomaar te verwijderen wanneer je een pagina opnieuw bewerkt!