I needed some practice with Java and Eclipse, and I love untangling legacy code, so a coworker recommended me the Gilded Rose Kata. I used Wouter Lagerweij’s Java port as my starting point.

The kata’s README specifies the system’s current behavior, constraints on changing it, and a desired new behavior. Since the spec wasn’t executable, I couldn’t trust its accuracy; since no executable spec existed, I couldn’t verify its accuracy; and since the code was carefully written to be opaque, I didn’t think it was worth making an effort to convince myself I understood it. At this point, I knew only two things:

  1. Not going anywhere without some characterization tests
  2. Disallowing something makes me really, really motivated to carry out its nearest permissible approximation

After writing enough characterization tests to load the domain lingo into my head and support (mostly) my reading of the spec, I looked again at the system and at the desired new behavior. Not seeing an obvious place to put it, I instead took the opportunity — afforded by my new tests — to perform some obvious refactorings until I spotted an opening just big enough to test-drive through. And then I chopped up the concerns scattered throughout the characterization tests and reconstituted them, filtered through my newfound understanding of the system, as the one-new-thing-at-a-time unit tests I probably would’ve written to begin with.

None of this is original or special. Anyone who’s danced with legacy code knows the ungainly rhythm of this dance. And while Gilded Rose is a very nicely designed exercise, it doesn’t compare in scope or risk to a real production system. What might be interesting, since it was just an exercise, is how I decided when to stop.

Given the state of my code, I don’t love:

  • The stupid not-allowed-to-get-rid-of-or-change-in-any-way Item class and items property
  • The Primitive Obsession that remains
  • The qualityIncrement action-at-a-distance
  • The hardcoded names of items with special behaviors
  • The domain concepts (like “legendary” or “conjured”) not yet explicitly represented in tests or code

And I’m happy with:

  • The small size of each step I took
  • The utility of my tests as they existed at each step
  • The increase in my work speed (partly from tests, partly from plain old practice with the IDE)
  • The new feature as safely delivered
  • The unit tests as they now stand
  • The likelihood that, given some new desired behavior, I or someone else could quickly and safely implement it

Remember, the assignment was to implement a new behavior. All that other stuff I did was enzymatic, enabling the assignment to be digested by my limited arrangement of brain cells. See how mixed that metaphor got? That’s why.

Anyway, I not only implemented the new behavior, but I did it in a way that felt safe throughout and that, were there a next developer, would afford them safety too. If this were a real production system, nothing in the “I don’t love” list would make me want to keep the new behavior out of people’s hands.

So that’s where I stopped. Because software development is always an exercise. We pick a point when we’ve learned enough to produce enough value, a point where more learning isn’t worth more cost, and we stop there. Every product and project leaves value on the table. I won’t pretend my “I don’t love” list doesn’t concern me (it does) or that it doesn’t matter (it does), but I’ll admit it doesn’t concern me or matter enough to be worth more cost. Every chance I get to make that decision wisely is worth practicing.