# TDD Katas Collection A collection of Test-Driven Development (TDD) exercises implementing classic programming katas, following Uncle Bob's Clean Code principles and Domain-Driven Design tactical patterns. ## Purpose These katas demonstrate: - **Test-Driven Development:** Production code written only to pass failing tests - **Clean Code Practices:** Intent-revealing names, single responsibility, domain-focused abstractions - **Domain-Driven Design:** Value Objects enforce domain constraints at the type level - **The Craftsman's Way:** Quality is not a trade-off for speed; it is the only way to go fast ## Completed Katas ### 1. Roman Numerals ✅ **Implementation:** [`lib/roman_numerals.dart`](lib/roman_numerals.dart) **Tests:** [`test/roman_numerals_test.dart`](test/roman_numerals_test.dart) Converts integers (1-3999) to Roman numeral notation using a table-driven greedy algorithm. #### Domain Context Roman numerals represent numbers using seven basic symbols with specific combination rules: **Symbols:** - `I` = 1, `V` = 5, `X` = 10, `L` = 50, `C` = 100, `D` = 500, `M` = 1000 **Domain Rules:** 1. **Additive Notation:** Symbols placed in descending order are summed (e.g., `VI` = 6) 2. **Subtractive Notation:** Smaller symbol before larger subtracts (e.g., `IV` = 4) 3. **Repetition Limit:** Symbols repeat maximum three times (e.g., `III` = 3, not `IIII`) 4. **Valid Range:** Classical Roman numerals represent 1-3999 **Subtractive Pairs (Domain Constraint):** Only specific pairs use subtractive notation: - `IV` (4), `IX` (9) - `XL` (40), `XC` (90) - `CD` (400), `CM` (900) #### Key Design Decisions **Value Object Pattern:** `RomanNumeralInput` enforces domain invariants (1-3999 range). Invalid inputs are impossible to construct. **Table-Driven Algorithm:** The `conversionRules` table is the **domain model**—it directly represents Roman numeral encoding rules. **Greedy Decomposition:** The algorithm mirrors how Romans actually encoded numbers: repeatedly subtract the largest applicable value. #### Usage ```dart import 'package:tdd_katas/roman_numerals.dart'; integerToRoman(1994); // Returns: 'MCMXCIV' integerToRoman(0); // Throws: ArgumentError ``` --- ### 2. Bowling Game ✅ **Implementation:** [`lib/bowling_game.dart`](lib/bowling_game.dart) **Tests:** [`test/bowling_game_test.dart`](test/bowling_game_test.dart) Calculates scores for a bowling game following official scoring rules with look-ahead bonus logic for strikes and spares. #### Domain Context A bowling game consists of 10 frames where players roll a ball to knock down pins: **Scoring Rules:** 1. **Normal Frame:** Sum of pins knocked down (e.g., 3 + 4 = 7) 2. **Spare (/):** All 10 pins in 2 rolls → Score = 10 + next 1 roll 3. **Strike (X):** All 10 pins in 1 roll → Score = 10 + next 2 rolls 4. **10th Frame:** Bonus rolls awarded if spare or strike achieved #### Key Design Decisions **State Management:** Stores all rolls in a list and calculates score by iterating through frames, not individual rolls. **Look-Ahead Logic:** Spares and strikes require examining future rolls for bonus calculation—the algorithm walks forward strategically. **Frame Advancement:** Strikes consume 1 roll, spares/normal frames consume 2 rolls—the algorithm tracks position correctly. #### Usage ```dart import 'package:tdd_katas/bowling_game.dart'; final game = BowlingGame(); game.roll(10); // Strike! game.roll(3); game.roll(4); // ... continue rolling print(game.score()); // Calculates total with bonuses ``` #### The "Aha!" Moment Tests for simple cases (gutter game, one spare, one strike) drove an algorithm that automatically handles complex scenarios like perfect games (300 points) without explicit implementation. **This is TDD's magic—correct abstractions emerge naturally.** --- ### 3. Gilded Rose ✅ **Implementation:** [`lib/gilded_rose.dart`](lib/gilded_rose.dart) **Tests:** [`test/gilded_rose_test.dart`](test/gilded_rose_test.dart) A legacy code refactoring kata demonstrating how to safely transform deeply nested conditionals into clean, extensible code using characterization tests and the Strategy pattern. #### Domain Context An inn's inventory system that updates item quality daily based on complex business rules: **Item Types:** 1. **Normal Items:** Quality decreases by 1/day, 2/day after sell-by date 2. **Aged Brie:** Quality increases by 1/day, 2/day after expiration (improves with age!) 3. **Sulfuras (Legendary):** Never changes, quality always 80, never "expires" 4. **Backstage Passes:** Complex appreciation: - More than 10 days: +1 quality/day - 10 days or less: +2 quality/day - 5 days or less: +3 quality/day - After concert (sellIn < 0): Quality drops to 0 5. **Conjured Items:** Degrade twice as fast as normal items (2/day, 4/day after expiration) **Domain Constraints:** - Quality never negative (≥ 0) - Quality never exceeds 50 (except Sulfuras at 80) - Cannot modify the `Item` class (goblin constraint!) #### Key Design Decisions **Characterization Testing:** Before touching legacy code, created 17 tests to capture existing behavior as a "safety net." Includes a Golden Master test simulating 30 days. **Strategy Pattern:** Each item type gets its own updater class implementing `ItemUpdater` interface. Eliminates conditional branching and enables Open-Closed Principle. **Helper Methods & Constants:** `_degradeQuality()` and `_improveQuality()` with automatic clamping eliminate scattered boundary checks. Domain constants (`_minQuality`, `_maxQuality`) remove magic numbers. **Factory Pattern:** `_selectUpdater()` method chooses the appropriate strategy based on item name, enabling polymorphic dispatch. #### The Refactoring Journey **Phase 1: Understand Legacy Code** ```dart // 60+ lines of 7-8 level nested conditionals // Nearly incomprehensible logic mixing all item types ``` **Phase 2: Characterization Tests (GREEN Phase)** - 14 comprehensive tests covering all item types - Golden Master test: 30-day simulation baseline - Result: **Safety net established** ✅ **Phase 3: Refactor with Confidence (3 steps)** **Step 1 - Extract Methods (REFACTOR):** ```dart // Before: 60 lines of nested hell // After: Clean if-else delegating to 4 private methods _updateNormalItem(), _updateAgedBrie(), _updateBackstagePasses(), _updateSulfuras() ``` **Step 2 - Strategy Pattern (REFACTOR):** ```dart abstract class ItemUpdater { void update(Item item); } class NormalItemUpdater implements ItemUpdater { ... } class AgedBrieUpdater implements ItemUpdater { ... } // ... 4 concrete strategies ``` **Step 3 - Domain Helpers (REFACTOR):** ```dart const int _minQuality = 0; const int _maxQuality = 50; void _degradeQuality(Item item, int amount) { item.quality = (item.quality - amount).clamp(_minQuality, _maxQuality); } ``` **Phase 4: Add Conjured Items (RED-GREEN)** **RED:** Added 3 failing tests for Conjured items behavior **GREEN:** Created `ConjuredItemUpdater` class—**one class, one condition** **Result:** Feature added in minutes thanks to refactoring! #### Usage ```dart import 'package:tdd_katas/gilded_rose.dart'; final items = [ Item('Normal Sword', 10, 20), Item('Aged Brie', 2, 0), Item('Sulfuras, Hand of Ragnaros', 0, 80), Item('Backstage passes to a TAFKAL80ETC concert', 15, 20), Item('Conjured Mana Cake', 3, 6), ]; final gildedRose = GildedRose(items); gildedRose.updateQuality(); // Updates all items per domain rules ``` #### The "Aha!" Moments 1. **Characterization Tests = Freedom:** With tests in place, aggressive refactoring felt safe. Every change validated instantly. 2. **Strategy Pattern = Extensibility:** Adding Conjured items took **5 minutes**. Before refactoring, it would have meant diving into nested conditionals and risking bugs. 3. **Small Steps = Big Wins:** Three refactoring commits transformed spaghetti into clean code. Each step kept tests GREEN, proving behavior preservation. 4. **Open-Closed Principle in Action:** New item types don't modify existing code—they just add new updater classes. The system is "open for extension, closed for modification." --- ### 4. String Calculator ✅ **Implementation:** [`lib/string_calculator.dart`](lib/string_calculator.dart) **Tests:** [`test/string_calculator_test.dart`](test/string_calculator_test.dart) A Bug Hunt Kata demonstrating how to use TDD to discover and fix bugs in existing code. Each bug is exposed with a RED test, then fixed with GREEN implementation. #### Domain Context A simple calculator that sums numbers from a string input with various delimiter support: **Features:** 1. **Empty String:** Returns 0 2. **Single Number:** Returns that number (`"5"` → 5) 3. **Comma Delimiter:** Sums comma-separated numbers (`"1,2,3"` → 6) 4. **Custom Delimiters:** Supports format `"//[delimiter]\n[numbers]"` (`"//;\n1;2"` → 3) 5. **Ignore Large Numbers:** Numbers > 1000 are ignored (`"2,1001"` → 2) #### The Bug Hunt Approach **Different from Previous Katas:** This wasn't built test-first. Instead, we started with **buggy working code** and used tests to expose and fix bugs one by one. **Bug Hunt Process:** 1. **RED:** Write test exposing a specific bug 2. **GREEN:** Fix only that bug 3. **Commit:** Document the bug found and fixed 4. **Repeat:** Move to next bug #### Bugs Found & Fixed **Bug #1: Empty String Returns Wrong Value** - **Bug:** Returned 1 instead of 0 - **Test:** `expect(calculator.add(''), equals(0))` - **Fix:** Changed return value from 1 to 0 - **Commits:** RED → GREEN **Bug #2: Single Number Off-By-One** - **Bug:** Added +1 to parsed number - **Test:** `expect(calculator.add('5'), equals(5))` - **Expected:** 5, **Actual:** 6 - **Fix:** Removed `+ 1` from parsing - **Commits:** RED → GREEN **Bug #3: Summation Loop Misses Last Element** - **Bug:** Loop condition `i < length - 1` skipped last item - **Test:** `expect(calculator.add('1,2'), equals(3))` - **Expected:** 3, **Actual:** 1 - **Fix:** Changed to `i < length` - **Commits:** RED → GREEN **Bug #4: Custom Delimiter Not Extracted** - **Bug:** Delimiter extraction line was commented out - **Test:** `expect(calculator.add('//;\n1;2'), equals(3))` - **Error:** FormatException trying to parse '1;2' - **Fix:** Uncommented `delimiter = parts[0].substring(2)` - **Commits:** RED → GREEN **Bug #5: Missing Feature - Ignore Numbers > 1000** - **Bug:** All numbers included in sum - **Test:** `expect(calculator.add('2,1001'), equals(2))` - **Expected:** 2, **Actual:** 1003 - **Fix:** Added `.where((n) => n <= 1000)` filter - **Commits:** RED → GREEN #### Usage ```dart import 'package:tdd_katas/string_calculator.dart'; final calculator = StringCalculator(); calculator.add(''); // Returns: 0 calculator.add('5'); // Returns: 5 calculator.add('1,2,3'); // Returns: 6 calculator.add('//;\n1;2'); // Returns: 3 calculator.add('2,1001'); // Returns: 2 (1001 ignored) ``` #### The "Aha!" Moments 1. **Tests as Bug Detectors:** Each test acted like a spotlight, illuminating exactly ONE bug at a time. No guessing—the test tells you what's broken. 2. **RED-GREEN Still Works:** Even when fixing bugs (not adding features), the RED-GREEN rhythm provides safety. You're never fixing blind. 3. **Regression Prevention:** After fixing each bug, ALL previous tests stay green. This proves you didn't break something while fixing something else. 4. **Incremental Debugging:** Fixing one bug at a time with commits creates a clear audit trail. You can see exactly what each bug was and how it was fixed. 5. **Real-World Skill:** This mirrors production work—most code you touch is existing code with bugs, not greenfield TDD. --- ### 5. Mars Rover ✅ **Implementation:** [`lib/mars_rover.dart`](lib/mars_rover.dart) **Tests:** [`test/mars_rover_test.dart`](test/mars_rover_test.dart) A Command Pattern kata simulating a robotic rover navigating a plateau on Mars. Demonstrates clean separation of concerns, value objects, and command-based control. #### Domain Context A rover explores a rectangular plateau with coordinate-based navigation: **Core Concepts:** - **Position:** (x, y) coordinates on the plateau grid - **Direction:** Cardinal directions (N, E, S, W) - **Plateau:** Grid with defined boundaries that wrap around (toroidal topology) **Commands:** - `L` - Turn left 90 degrees (changes direction, not position) - `R` - Turn right 90 degrees (changes direction, not position) - `M` - Move forward one grid point in current direction **Example Navigation:** ``` Starting: (0,0) facing North Commands: "MMRMMLM" - MM: Move to (0,2) facing North - R: Turn to face East (still at 0,2) - MM: Move to (2,2) facing East - L: Turn to face North (still at 2,2) - M: Move to (2,3) facing North Result: (2,3) facing North ``` #### Key Design Decisions **Direction Enum:** Encapsulates rotation logic using modular arithmetic. Each direction knows how to turn left/right, eliminating conditional branching. **Value Objects:** - `Position` is immutable—movement returns new position instances - `Plateau` encapsulates boundary wrapping logic - Prevents invalid states at the type level **Command Pattern (Implicit):** The `execute()` method delegates to command handlers (`turnLeft()`, `turnRight()`, `moveForward()`). Each command is isolated and testable. **Wrapping Logic:** Plateau boundaries wrap around (toroidal topology). Moving past edge (e.g., x=5→6 on 5x5 grid) wraps to opposite side (x=0). #### Usage ```dart import 'package:tdd_katas/mars_rover.dart'; // Create rover at position (1,2) facing North on 5x5 plateau final rover = Rover( x: 1, y: 2, direction: 'N', plateauWidth: 5, plateauHeight: 5, ); rover.execute('LMLMLMLMM'); print('Position: (${rover.x}, ${rover.y})'); // Position: (1, 3) print('Direction: ${rover.direction}'); // Direction: N ``` #### The "Aha!" Moments 1. **Enums as Behavior Carriers:** Direction enum doesn't just store values—it encapsulates rotation logic. Turning left/right becomes `direction.turnLeft()`, eliminating lookup tables. 2. **Value Objects Prevent Bugs:** Immutable `Position` means movement can't corrupt state. New position calculated, validated, then assigned. Boundary wrapping isolated in `Plateau`. 3. **Switch Expressions Shine:** Modern Dart's `switch` expression makes direction-based movement elegant and exhaustive. Compiler enforces handling all directions. 4. **Modular Arithmetic for Rotation:** `(index + 1) % 4` handles right rotation elegantly. No if-statements, no edge cases—math models the domain perfectly. 5. **Refactoring Without Fear:** Two refactoring commits drastically improved code structure. Tests stayed green throughout, proving behavior preservation. --- ## Running Tests ```bash # Run all tests dart test # Run specific test file dart test test/roman_numerals_test.dart dart test test/bowling_game_test.dart dart test test/gilded_rose_test.dart dart test test/string_calculator_test.dart dart test test/mars_rover_test.dart # Run with coverage dart test --coverage ``` ## Development Philosophy ### The Craftsman's Standard > "Clean code that works." — Ron Jeffries Every kata in this collection follows: - **Uncle Bob's Clean Code:** Intent-revealing names, functions do one thing, no comments needed - **Kent Beck's TDD:** Red-Green-Refactor discipline, tests first - **Eric Evans' DDD:** Domain concepts drive the model, tactical patterns enforce boundaries - **The Boy Scout Rule:** Every commit leaves the code cleaner than before ### TDD Discipline Applied 1. **Red:** Write a failing test 2. **Green:** Write the simplest code to pass 3. **Refactor:** Clean up duplication, improve names 4. **Repeat:** Let the design emerge from tests --- ## Roman Numerals: Detailed Journey ### Test Strategy Tests are organized by **domain concepts**, not technical structure: **Basic Symbols:** Tests for the seven fundamental symbols (I, V, X, L, C, D, M) **Subtractive Notation:** Tests for all six subtractive pairs, verifying the domain rule **Additive Combinations:** Tests for repeated symbols and multi-symbol sequences **Complex Edge Cases:** Stress tests combining multiple rules: - `1994 → MCMXCIV` (year notation) - `3999 → MMMCMXCIX` (maximum valid value) - `444 → CDXLIV` (all subtractive positions) **Constraint Validation:** Boundary tests for the valid range (1-3999) ### Development Timeline 1. **Red:** Tests for 1-5 (basic additive, first subtractive case) 2. **Green:** Minimal implementation with conditionals 3. **Refactor:** Extract symbol mapping, clarify intent 4. **Red:** Tests for 6-10 (reveals pattern) 5. **Green:** Extend conditionals 6. **Refactor:** Recognize duplication → Table-driven approach emerges 7. **Red:** Tests for 40-1000 (remaining symbols) 8. **Green:** Extend conversion table (algorithm unchanged) 9. **Red:** Edge cases and constraint tests 10. **Green:** Add `RomanNumeralInput` Value Object 11. **Refactor:** Extract validation, organize tests by domain concept ### Key Insights **The Algorithm Never Changed:** After the table-driven refactoring, adding 40-1000 required **zero logic modifications**. This validates the abstraction. **Type System as Domain Enforcer:** `RomanNumeralInput` makes invalid states unrepresentable. You cannot construct a Roman numeral for 0 or 4000—the compiler prevents it. **Tests as Living Documentation:** Test names use **ubiquitous language** from the Roman numeral domain. A domain expert could read the test file and recognize the rules they explained. --- ## Bowling Game: Detailed Journey ### Test Strategy Tests are organized by **scoring complexity**, mirroring how the domain rules build on each other: **Basic Scoring:** - Gutter game (all zeros) - All ones (simple addition) **Spare Bonus (next 1 roll):** - One spare in first frame - All spares (150 points) **Strike Bonus (next 2 rolls):** - One strike in first frame - Perfect game (300 points) **Complex Scenarios:** - Combinations of strikes, spares, and normal frames ### Development Timeline 1. **Red:** Gutter game test 2. **Green:** Return 0 (simplest implementation) 3. **Red:** All ones test 4. **Green:** Store rolls, sum them in `score()` 5. **Refactor:** Extract `rollMany()` helper, add `setUp()` 6. **Red:** One spare test 7. **Green:** Detect spare, add look-ahead bonus (+1 roll) 8. **Refactor:** Extract `_isSpare()` helper 9. **Red:** One strike test 10. **Green:** Detect strike, add look-ahead bonus (+2 rolls) 11. **Refactor:** Extract `_isStrike()`, clean up frame advancement 12. **Validate:** Perfect game test passes without modification! ### Key Insights **Emergent Design:** The algorithm structure wasn't planned upfront. Tests for simple cases forced: - Frame-based iteration (not roll-based) - Index tracking (advancing by 1 or 2) - Look-ahead logic (accessing future rolls) **The Perfect Game Moment:** Writing code to handle "one spare" and "one strike" automatically handled "12 consecutive strikes" (300 points). The algorithm correctly models the domain, so all valid games work. **State vs. Behavior:** Initially tempting to model Frame objects with state. TDD revealed a simpler truth: just store rolls and calculate on-demand. No frame objects needed. --- ## Gilded Rose: Detailed Journey ### The Legacy Code Challenge Unlike the previous two katas (greenfield TDD), Gilded Rose simulates **real-world legacy code refactoring**. You inherit messy, working code with no tests and must: 1. Understand what it does (without breaking it) 2. Add tests to capture behavior 3. Refactor safely 4. Add new features ### Test Strategy Tests are organized by **item type behavior** and include a **Golden Master**: **Normal Items:** - Quality degradation (1/day, 2/day after expiration) - Quality never negative **Aged Brie:** - Quality appreciation (improves with age) - Respects quality cap (≤ 50) **Sulfuras (Legendary Items):** - Never changes (quality, sellIn) - Always quality 80 **Backstage Passes:** - Threshold-based appreciation (10 days, 5 days) - Drops to 0 after concert **Conjured Items (new feature):** - Degrades 2x faster than normal items **Golden Master Test:** - 30-day simulation with all item types - Captures baseline output before refactoring - Detects any behavioral regression ### Refactoring Timeline **Phase 1: Create Legacy Code** - Intentionally nested 7-8 levels deep - Mixed concerns (all item types in one method) - Magic numbers scattered throughout - Result: Represents realistic legacy code **Phase 2: Characterization Tests (GREEN)** - 14 comprehensive tests written before any refactoring - Golden Master baseline captured - Commit: `"GREEN: Add characterization tests"` - Result: **Safety net established** ✅ **Phase 3: Refactor in Small Steps (3 REFACTOR commits)** **Step 1 - Extract Methods:** ```dart // Before: 60 lines, items[i] everywhere, deeply nested for (var i = 0; i < items.length; i++) { if (items[i].name != 'Aged Brie' && ...) { if (items[i].quality > 0) { if (items[i].name != 'Sulfuras...') { // ... 5 more levels ... // After: Clean delegation, readable for (final item in items) { if (item.name == 'Sulfuras, Hand of Ragnaros') { _updateSulfuras(item); } else if (item.name == 'Aged Brie') { _updateAgedBrie(item); // ... } ``` Commit: `"REFACTOR: Extract item type methods from nested conditionals"` **Step 2 - Introduce Strategy Pattern:** ```dart abstract class ItemUpdater { void update(Item item); } class NormalItemUpdater implements ItemUpdater { @override void update(Item item) { _degradeQuality(item, 1); item.sellIn -= 1; if (item.sellIn < 0) { _degradeQuality(item, 1); } } } // ... 4 concrete strategies ItemUpdater _selectUpdater(Item item) { ... } ``` Commit: `"REFACTOR: Introduce Strategy pattern for item types"` **Step 3 - Extract Domain Helpers:** ```dart const int _minQuality = 0; const int _maxQuality = 50; void _degradeQuality(Item item, int amount) { item.quality = (item.quality - amount).clamp(_minQuality, _maxQuality); } void _improveQuality(Item item, int amount) { item.quality = (item.quality + amount).clamp(_minQuality, _maxQuality); } ``` Commit: `"REFACTOR: Extract helper methods and domain constants"` All 14 tests stayed **GREEN** throughout! 🟢 **Phase 4: Add Conjured Items (RED-GREEN-REFACTOR)** **RED:** Write failing tests ```dart test('degrade in quality twice as fast as normal items', () { final items = [Item('Conjured Mana Cake', 10, 20)]; GildedRose(items).updateQuality(); expect(items[0].quality, equals(18)); // -2 instead of -1 }); ``` Result: 2 tests **FAILING** ❌ (Expected: 18, Actual: 19) Commit: `"RED: Add failing tests for Conjured items"` **GREEN:** Implement minimal solution ```dart class ConjuredItemUpdater implements ItemUpdater { @override void update(Item item) { _degradeQuality(item, 2); // 2x normal rate item.sellIn -= 1; if (item.sellIn < 0) { _degradeQuality(item, 2); // 4x total after expiration } } } ItemUpdater _selectUpdater(Item item) { // ... existing checks ... } else if (item.name.startsWith('Conjured')) { return ConjuredItemUpdater(); } else { return NormalItemUpdater(); } } ``` Result: All 17 tests **PASSING** ✅ Commit: `"GREEN: Implement Conjured items degrading twice as fast"` **REFACTOR:** Already clean! No duplication, clear names, reusing helpers. Decision: Skip refactor commit—code is already excellent. ### Development Metrics | Metric | Before Refactoring | After Refactoring | |--------|-------------------|-------------------| | **Cyclomatic Complexity** | ~25 (very high) | ~3 per class (low) | | **Lines per Method** | 60+ | 5-10 | | **Max Nesting** | 7-8 levels | 2 levels | | **Time to Add Feature** | Hours (risky) | Minutes (safe) | | **Test Coverage** | 0% → 100% | 100% (maintained) | ### Key Insights **Characterization Tests Are Your Lifeline:** Without tests, refactoring is guesswork. With tests, it's engineering. Every change validated in milliseconds. **Small Steps = Low Risk:** Three refactoring commits, each preserving behavior. No "big bang" rewrite—steady, safe progress. **Strategy Pattern = Future-Proofing:** Adding Conjured items demonstrated the payoff: - **Before refactoring:** Would require diving into nested conditionals, risking bugs - **After refactoring:** One new class, one condition, done in 5 minutes **Golden Master Testing:** The 30-day simulation test caught edge cases that individual unit tests missed. It serves as a comprehensive regression detector. **Open-Closed Principle Validated:** New item types extend the system without modifying existing updater classes. The design is "open for extension, closed for modification." **Refactoring ≠ Rewriting:** We never changed what the code does, only how it's structured. Tests prove behavioral equivalence at every step. --- ## Comparing the Katas ### All Five Katas at a Glance | Aspect | Roman Numerals | Bowling Game | Gilded Rose | String Calculator | Mars Rover | |--------|----------------|--------------|-------------|-------------------|------------| | **Complexity** | Beginner | Intermediate | Advanced | Beginner | Intermediate | | **Approach** | Greenfield TDD | Greenfield TDD | Legacy refactoring | Bug hunting | Greenfield TDD | | **State** | Stateless | Stateful | Stateful | Stateless | Stateful | | **Algorithm** | Table-driven | Frame iteration | Strategy pattern | String parsing | Command pattern | | **Key Challenge** | Pattern recognition | State & bonuses | Refactoring safely | Finding bugs | Navigation & wrapping | | **Design Pattern** | Value Object | Implicit strategy | Explicit strategy | Filters & pipes | Command + Value Objects | | **Lines of Code** | ~45 production | ~30 production | ~120 production | ~25 production | ~95 production | | **Test Count** | ~15 tests | ~10 tests | ~17 tests | 6 tests | 23 tests | | **Aha! Moment** | Table = data | Simple → complex | Refactor = safe | Tests find bugs | Enums carry behavior | ### What Each Kata Teaches **Roman Numerals:** **Roman Numerals:** - Converting domain rules into data structures - Value Objects for enforcing constraints - When to stop coding (algorithm emerges naturally) **Bowling Game:** - State management without over-engineering - Look-ahead logic in sequential data - How correct abstractions scale beyond test cases **Gilded Rose:** - Safely refactoring legacy code with characterization tests - Strategy pattern for eliminating conditional complexity - Open-Closed Principle for extensibility - Working effectively with code you didn't write **String Calculator:** - Using tests to expose bugs in existing code - Bug hunting with RED-GREEN discipline - Incremental debugging with clear commits - Regression prevention through test accumulation **Mars Rover:** - Command pattern for behavior delegation - Value Objects for domain modeling (Position, Plateau, Direction) - Enums as behavior carriers, not just constants - Coordinate systems and wrapping logic - Progressive refactoring with confidence ### Progressive Learning Path 1. **Roman Numerals first:** Learn TDD fundamentals without state complexity 2. **Bowling Game second:** Apply TDD to stateful problems 3. **Mars Rover third:** Master Command pattern and value objects 4. **Gilded Rose fourth:** Refactor legacy code with tests as safety net 5. **String Calculator fifth:** Practice bug hunting and fixing with TDD 6. **Next kata:** Choose based on what you want to practice: - **Prime Factors:** Mathematical decomposition, algorithmic thinking - **Tennis Scoring:** State machines, domain language - **FizzBuzz:** Classic conditional logic exercise --- ## References ### General TDD & Clean Code - [Gilded Rose Kata](https://github.com/emilybache/GildedRose-Refactoring-Kata) - [Clean Code by Robert C. Martin](https://www.oreilly.com/library/view/clean-code-a/9780136083238/) - [Domain-Driven Design by Eric Evans](https://www.domainlanguage.com/ddd/) - [Test-Driven Development by Kent Beck](https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530) ### Kata-Specific - [Roman Numerals Rules](https://en.wikipedia.org/wiki/Roman_numerals) - [Bowling Scoring Rules](https://en.wikipedia.org/wiki/Ten-pin_bowling#Scoring) - [Uncle Bob's Bowling Game Kata](http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata) ## License This is a learning exercise. Use freely for educational purposes. --- **Following The Craftsman's Way: Quality is not negotiable.**