Exercises
Exercises are organized in four lanes. Work the lanes in order; each lane targets a specific fluency, not a specific concept. Completion is per-exercise, not per-lane.
Conventions used below:
★= foundational;★★= stretch (judgment heavy).- Every exercise says what the success check is -- the refactor is not done until that check passes.
Lane 1 -- Small Moves Fluency
Target outcome: Named-move fluency on short snippets. After this lane you should be able to name, motivate, and execute Extract Function, Inline Function, Extract Variable, Rename, and Change Function Declaration without looking at a reference.
Exercise 1.1 ★ -- Extract Until It Reads Like Prose
Start from Fowler's statement function at the beginning of chapter 1.
- Chunks: 006, 007, 008, 009.
- Your task: Reproduce the chapter's decomposition, but stop before step 5 (the polymorphic calculator). Commit after every named step. Each commit title must name the move (e.g., "Extract Function: amountFor").
- Success check:
git log --onelinereads like Fowler's step list. Tests stay green every commit.
Exercise 1.2 ★ -- Rename Only
Find a file in your own codebase (or a fork of a small OSS repo) with at least three poorly named identifiers. Rename them one per commit, tests green between.
- Chunks: 042, 045.
- Success check: A reviewer reading only the diff can describe each rename's motivation from the before/after code.
Exercise 1.3 ★★ -- Change Function Declaration Without Breaking Callers
Pick a function in your codebase whose parameter list you've wanted to change (reorder, add, drop). Execute a parallel change: add the new signature alongside, migrate callers in batches, retire the old.
- Chunks: 043.
- External: ParallelChange.
- Success check: Every intermediate commit is deployable. No commit contains both the new signature and a deletion of the old.
Lane 2 -- Data Reorganization
Target outcome: Recognize the data-smell ↔ move mapping. After this lane you should see "primitive obsession" or "data clump" and immediately reach for the right move.
Exercise 2.1 ★ -- Encapsulate Record
Find a record (dict, struct, JSON object) used in more than ten read sites. Wrap it in a class that exposes accessors.
- Chunks: 049, 050.
- Success check: Rename one of the record's underlying fields without changing any read site. Tests still green.
Exercise 2.2 ★★ -- Replace Primitive with Object
Find a primitive in your code that has at least three format/validate/compare helpers scattered around it (formatPriority, isPriorityHigh, parsePriority). Wrap it in a value object and migrate.
- Chunks: 052.
- Success check: All three helpers are now methods on the value object. The primitive never appears outside the object's
valueaccessor except at the system boundary (DB, API).
Exercise 2.3 ★ -- Introduce Parameter Object
Find three functions that share a parameter list of two or more values. Introduce a parameter object.
- Chunks: 045 (second half).
- Success check: Each of the three signatures now takes a single object parameter. No callers pass the old triple-parameter form.
Lane 3 -- Conditional Reshaping
Target outcome: Choose between decompose / consolidate / guard clauses / polymorphism based on the shape of the branching, not based on which one you learned last.
Exercise 3.1 ★ -- Decompose and Consolidate
Find a function with (a) a single multi-line conditional and (b) three guards that all return the same sentinel. Apply Decompose Conditional to (a), Consolidate Conditional Expression to (b).
- Chunks: 066, 067.
- Success check: The function reads as a sequence of named predicates and named actions. No branch is more than 4 lines after the move.
Exercise 3.2 ★★ -- Replace Conditional with Polymorphism
Find (or fabricate) at least two functions in the same module that switch on the same discriminator. Introduce a type hierarchy; push each function's branch into the relevant subclass.
- Chunks: 068, 069.
- Success check: Both
switchstatements are gone. Adding a third variant requires adding one new subclass and zero changes to existing functions.
Exercise 3.3 ★★ -- Guard Clauses
Find a function with two or more levels of nested ifs. Flatten with guard clauses.
- Chunks: 067.
- Success check: The remaining
ifs in the function are at a single level of nesting. The "happy path" is the last (unconditional) return.
Lane 4 -- Large-Change Strategies
Target outcome: You can plan and execute a change that is too big for a single commit without blocking deployment.
Exercise 4.1 ★ -- Parallel Change on a Rename
Pick a widely used function in your codebase. Rename it from X to Y using expand / migrate / contract. The old name must remain callable until the final step.
- Chunks: 043.
- External: ParallelChange.
- Success check: Every commit is deployable. The expand commit and the contract commit are separated by at least one migrate commit.
Exercise 4.2 ★★ -- Branch by Abstraction
Pick a dependency in your codebase that has two plausible implementations (two HTTP clients, two DB libraries, two loggers). Introduce an abstraction, migrate the caller to the abstraction, then (optionally) add a second implementation behind the abstraction.
- Chunks: 020.
- External: BranchByAbstraction.
- Success check: The abstraction is used by the caller in a deployable commit. Main never contains a half-migrated codebase where some sites call through the abstraction and some bypass it.
Exercise 4.3 ★★ -- Strangler Plan (Design, Not Code)
For a legacy system you know (yours or a documented one), write a one-page Strangler plan: proxy/boundary, slice order (three or four slices), cutover criteria per slice, and a retirement condition for the legacy code.
- External: StranglerFigApplication, patterns-legacy-displacement.
- Success check: A senior engineer reading the plan can explain what deploys in week 1, what deploys in week 4, and when (under what condition) the legacy code is deleted.
Completion Checklist
Your module is complete when all apply:
- Lane 1 exercises 1.1 and 1.2 each produce a clean commit history with one named move per commit
- Lane 1 exercise 1.3 has no commit that combines expand and contract
- Lane 2 exercises each pass their rename / migration success checks
- Lane 3 exercise 3.2 survives the "add a third variant" test with no changes to existing functions
- Lane 4 exercise 4.1 shows three distinct commit groups (expand, migrate, contract)
- Lane 4 exercise 4.3 plan has been read by one other engineer and they understood the sequence
- For every exercise, tests were green between every commit -- no red-green-commit-red-green-commit sequences
- You can articulate, for each named move used, what recurrence signal justified using it (not "it looked clean")