rgoussu@goussu: ~/library/engineering-practice/exercises
~/library/engineering-practice/exercises cat gilded-rose-technical-debt-workout-subject.md

Gilded Rose: a technical-debt workout — subject

# The full work statement for the debt-management cycle — the inn's inventory rules, the sealed-estimate protocol, golden-master rules, micro-commit repayment, the conjured-items feature, and the retro deliverables.

Subjectsaved 2026-08-08source #exercise#technical-debt#refactoring#legacy-code#estimation#subject

Brief

You have inherited the inventory system of the Gilded Rose, a small inn that buys and sells goods. Every item has a sellIn (days left to sell it) and a quality value; once a day the system updates both for every item. The previous developer is gone; the code is one dense conditional thicket that nobody dares touch, and there are no tests. The goblin in the corner owns the Item class and will not let you change it. The business has just sold a new category — conjured items — and needs the system to support them. Your job is not merely to add the feature: it is to run the whole episode as a managed debt-repayment cycle, producing the estimates, tests, commits, and documents a well-run team would keep.

The domain rules (the legacy behavior you must preserve)

  • Each daily update: sellIn decreases by 1, and quality changes per the rules below.
  • Ordinary items lose 1 quality per day; once the sell-by date has passed (sellIn < 0), degradation doubles (2 per day).
  • quality is never negative, and never rises above 50 — with one exception below.
  • Aged Brie gains quality as it ages instead of losing it (and, per the existing code's actual behavior, gains twice as fast after the sell-by date — the tests you write will tell you; bugs included is the contract).
  • Sulfuras, Hand of Ragnaros is legendary: never has to be sold, never changes — sellIn untouched, quality fixed at 80 (the exception to the 50 cap).
  • Backstage passes gain quality as the concert nears: +1 normally, +2 at 10 days or fewer, +3 at 5 days or fewer, and drop to 0 after the concert (sellIn < 0).
  • New requirement — Conjured items degrade twice as fast as ordinary items (2 per day before sell-by, 4 after), respecting the same 0 floor.

Instructions

Work through the five phases in order; do not peek ahead (in particular, do not start reading the code "for the tests" before phase 1's estimates are sealed).

Phase 1 — Sealed estimates

  1. Pick a language from the kata repo and set the project up so it compiles/runs.
  2. Read the legacy update method cold — no refactoring, no test-writing yet.
  3. Write down two range estimates (best case / worst case, in hours):
    • E1: implementing conjured items directly on the legacy code, now;
    • E2: implementing conjured items after a cleanup, including the cleanup. For each, note in two or three lines what drives the range (what scares you).
  4. Seal them: commit the estimates file before any other work. It is not edited again until phase 5 — mispredictions are data, not embarrassment.

Phase 2 — Golden master

Pin the current behavior, bugs included, with a characterization harness:

  • Drive the system over a wide input grid — every special item name plus ordinary ones, sellIn and quality values crossing every boundary (negative, 0, 1, 5, 6, 10, 11, 49, 50, 80), updated over enough days to cross the sell-by line — and capture the full output as text.
  • The captured output is recorded, reviewed once by eye, then committed as the approved master; from then on the test fails on any diff. (The repo ships text-based/approval test support in most languages — use it or hand-roll the compare.)
  • Rules of construction: the master must be deterministic (no timestamps, no ordering wobble); assert on observable outputs only (names, sellIn, quality); don't "fix" anything the master captures — the current behavior is the spec.
  • Check coverage (line/branch) of the update logic; extend the grid until the legacy method is fully covered.

Phase 3 — Repay in micro-commits

Refactor toward a design that makes item-specific behavior explicit (polymorphism, strategy map, rule table — your call), under these cadence rules:

  • Every commit ≤ ~5 minutes of work and one refactoring idea (rename, extract method, invert condition, lift special case…); the golden master is green at every single commit.
  • Commit messages name the debt retired, honestly: "extract per-item update — removes 3-deep nesting on Brie path", not "wip".
  • If a step goes red and the fix isn't obvious within a couple of minutes, revert, don't debug — take a smaller step.
  • You may not modify the Item class or its fields (the goblin's constraint) — the design must absorb this.

Phase 4 — Ship conjured items

  • Implement the conjured-items rule on the cleaned design, tests first this time (real unit tests for the new behavior, alongside the master).
  • Record the actual time for the cleanup (phase 3) and the feature (phase 4) separately.

Phase 5 — Retro and business case

Produce three documents (versioned with the code):

  1. Estimate audit — the sealed E1/E2 vs. actuals: cleanup cost, feature-on-clean cost, and your honest guess of feature-on-legacy cost in hindsight. State the measured "interest rate" in one sentence: what the mess added to the price of one feature.
  2. Debt records — ADR-style entries, written as if you'd inherited the codebase mid-loan: for each major debt you found (at minimum: the mega-conditional, the string-typed item identities, the missing tests), record its Fowler quadrant (deliberate/inadvertent × prudent/reckless), the interest it was charging, and how (or whether) it was repaid.
  3. Business case — one page, addressed to a non-technical product owner: what this debt cost (N), what the cleanup cost (M), when it paid back, and what capacity you are asking for going forward. Numbers from your own measurements, not folklore.

Constraints

  • The Item class and its properties are untouchable, and you may not remove the shared items array — the kata's canonical constraint.
  • No behavior changes before the golden master is complete — bug-for-bug compatibility is the phase-2 contract.
  • The sealed estimates are never revised after phase 1; the retro compares against them as committed.

Acceptance

Mapped to the exercise's milestones:

  1. Estimates sealed — a committed estimates file (two ranges + rationale) whose commit predates every test and refactoring commit in the history.
  2. Golden master — mutation check: sabotage any single rule branch in the legacy code (flip a comparison, change a constant) and the harness fails; coverage of the update logic is total; two consecutive clean runs produce identical output.
  3. Micro-commits — the log from master-complete to cleanup-done is a chain of small green commits, each naming its refactoring; no commit mixes refactoring with behavior change; Item is untouched throughout.
  4. Feature — conjured items degrade 2/day before sell-by and 4/day after, never below 0; all pre-existing behavior still matches the master; actual times for phases 3 and 4 are recorded.
  5. Retro — the three documents exist and are self-supporting: the audit's numbers trace to commits/timestamps, every debt record carries a quadrant and an interest statement, and the business case fits on a page and quotes only measured numbers.

Related