Module Quiz
Complete this quiz after finishing all concept and practice pages. Answers explain why this move, not that one.
Current Module Questions
Question 1: Defining Refactoring
Fowler says "refactoring" is not the same as "cleaning up code." What is the one property that distinguishes refactoring from arbitrary restructuring, and why does it matter?
Answer: Behavior preservation. Each step leaves observable behavior unchanged, verified by a green test run. It matters because it is what makes a series of small steps composable -- you can stop at any moment and the program still works. Cleanup without that property can leave the code broken for hours or days.
Question 2: Two Hats
A pull request is titled "refactor order processing and fix the negative-total bug." Why is this one PR a defect in the PR itself, even if the code is correct?
Answer: It mixes two hats. A later regression cannot be bisected cleanly -- the PR contains a structural change and a behavior change. Bisect lands on the mixed commit, giving no useful signal. The fix should be two PRs (or two commits): one pure refactor with tests unchanged, one behavior fix with a new test.
Question 3: When to Refactor
A team asks for approval to "stop feature work for two weeks and refactor." Why is that usually a warning sign, and what should happen instead?
Answer: Scheduled refactor sprints usually get cancelled under pressure or fail to fix the actual daily friction. Refactoring belongs inline with regular work, driven by four triggers: rule of three, preparatory, comprehension, opportunistic. If a two-week dedicated push is genuinely needed, it is usually a large-change problem (Cluster 5: Branch by Abstraction or Strangler Fig), not generic cleanup.
Question 4: Characterization Tests
You inherit a function with no tests. You want to refactor it. Your coworker says "let's first write proper spec tests based on the requirements doc." Why is a characterization test usually better first?
Answer: The requirements doc and the actual behavior are rarely the same. A characterization test pins what the code does today, including any bugs. That is the baseline you need to prove refactor steps preserve behavior. After the refactor is done, you can fix the bugs in feature-hat commits and promote the characterization tests to specification tests.
Question 5: Seams
Given async function calculatePrice(order) { ... const shipping = await calculateShipping(order); ... }, you want to unit-test calculatePrice without hitting the network. Name two seams you could introduce and which has the smaller blast radius.
Answer: (a) Parameter seam: pass calculateShipping as an optional parameter (default to the real implementation). Enabling point is the caller. (b) Module-level rebinding: export a mutable binding the test can reassign via a reset function. Enabling point is the module import. The parameter seam has the smaller blast radius -- it changes one signature and one default; the rebinding touches module-level state across tests.
Question 6: Extract Function vs Extract Variable
You see a 40-character arithmetic expression used once inside a function. When is Extract Variable the right move, and when is Extract Function?
Answer: Extract Variable when the name is meaningful only inside this function -- you want a local label for a sub-computation. Extract Function when the same computation would be useful in another function in the module, or when the name describes a concept that belongs at the module level. Fowler's rule: local meaning -> variable; shared meaning -> function (or Replace Temp with Query later).
Question 7: Move Function
A function on class A uses this.type.* more than this.*. Which refactor, and what is the preferred sequence of steps?
Answer: Move Function to the AccountType (or equivalent) class. Sequence: copy the function into the target; adjust parameters; change the source into a delegating wrapper (return this.type.fn(...)); run tests; migrate callers to call the target directly; inline the delegating wrapper. Doing it in one "big" step without the delegator forces you to update all callers atomically.
Question 8: Split Phase vs Extract Function
Both seem to "break a function into parts." How are they different?
Answer: Extract Function separates intent from implementation -- the extracted piece still works with the same data shape as the caller. Split Phase separates sequential concerns that operate on different data shapes, using an intermediate record that phase 1 produces and phase 2 consumes. If your two "phases" share mutable state, you have not split; you have extracted.
Question 9: Encapsulate Record
You have a record {name, country} used by 40 read sites and 5 write sites. Why is Encapsulate Record worth the ceremony, given that the record "already works"?
Answer: Encapsulate Record gives the data a class, making three near-free: rename a field (callers use accessors), change storage (calculate country from a different source), and migrate the structure incrementally. Without encapsulation, every one of those changes is a ripple through 45 sites. The ceremony pays back the first time one of those changes is needed.
Question 10: Replace Primitive with Object -- When Not To
Fowler calls Replace Primitive with Object "one of the most valuable refactorings." Name a case where applying it would be the wrong move.
Answer: When the primitive has no recurring behavior attached to it -- e.g., a numeric id used only as a dictionary key, no formatting, no comparison, no derivation. Wrapping it would produce ceremony without benefit. The move pays off when scattered format/validate/compare logic begins accumulating around the primitive. If none exists, leave it a primitive.
Question 11: Polymorphism Misfire
You see a single switch (type) inside one function. A teammate proposes Replace Conditional with Polymorphism and wants to build a four-class hierarchy. What do you say, and what is the right move instead?
Answer: Polymorphism pays when the same switch recurs across multiple functions on the same discriminator. A single switch is not that. The right first move is Decompose Conditional: extract the condition and each branch into named functions. If later the switch pattern appears in a second function, revisit -- now the recurrence rule is satisfied and a hierarchy is justified.
Question 12: Parameter Object vs Preserve Whole Object
Two candidates:
A) (customer, startDate, endDate) appearing in three functions
B) fn(aRoom.temp.low, aRoom.temp.high) where the caller extracts both values from one object
Which gets Introduce Parameter Object and which gets Preserve Whole Object, and why?
Answer: A is Introduce Parameter Object -- there is no pre-existing object that bundles startDate + endDate. You create DateRange because the clump recurs. B is Preserve Whole Object -- the whole already exists (aRoom.temp), and the caller is unpacking it just to pass it. Pass the whole and let fn read what it needs.
Question 13: Decompose vs Consolidate
When you see three separate if guards returning the same value, which move applies, and what has to be true for the move to be safe?
Answer: Consolidate Conditional Expression: combine the guards into one ||-joined condition, then extract that into a named predicate. Safety requirement: none of the conditions has side effects. If any does, Separate Query from Modifier first, or leave the sequence alone. If the three guards encode genuinely independent rules likely to evolve separately, leaving them separate may be clearer.
Question 14: Parallel Change
A function circum(radius) is used in 40 places. You want to rename it to circumference(radius) without breaking any caller. Describe the three phases and what would go wrong if you skipped the contract phase.
Answer: Expand -- add circumference alongside circum, with circum delegating. No callers touched. Migrate -- change callers to circumference in batches. Contract -- delete circum once no caller remains. Skipping contract leaves both names alive permanently; the surface area doubles, and new code has to guess which to use. The original rename's purpose (one clean name) is not achieved.
Question 15: Strangler vs Big Rewrite
A team plans to replace a 300k-line monolith by building a green-field replacement over 18 months and cutting over in one day. What are the two strongest arguments for a Strangler Fig approach instead?
Answer: (1) Risk: a Strangler migration ships partial value continuously and each step is reversible at the proxy. A one-day cutover is a coin flip on a system whose real behavior is only partially documented. (2) Feedback: users continue to use the system through the migration, so each migrated slice is validated under real load and real edge cases. A 18-month dark build accumulates unverified assumptions that all surface on cutover day.
Interleaved Review Questions
Prior Module Question 1 -- S3M1: Cohesion and Coupling
Why does high cohesion within a module make refactoring easier and low coupling make it safer?
Answer: High cohesion means the module has one reason to change -- refactors stay local and you are not dragged across module boundaries. Low coupling means a change here has fewer observers -- fewer call sites break, fewer tests flip, and the refactor's blast radius is predictable. Both reduce the ripples that turn a small refactor into a big one.
Prior Module Question 2 -- S3M1: SOLID as Lenses
A class that passes SRP and OCP but violates LSP -- what kind of refactor in this module is most likely triggered, and why?
Answer: LSP violation typically signals that a subclass is misrepresenting its parent's contract. The appropriate refactors are Replace Subclass with Delegate or Replace Superclass with Delegate (inheritance -> composition). Replace Conditional with Polymorphism is usually not the answer; it assumes healthy polymorphism exists, while LSP is the sign that polymorphism here is broken.
Prior Module Question 3 -- S2: Abstractions and Invariants
When you Extract Function, you are imposing a new invariant at the boundary of the extracted function. What is that invariant, and why does violating it lead to refactor bugs?
Answer: The invariant is the function's precondition / postcondition pair -- the caller's state before the call, and the caller's state after. When you extract, all locals used inside the fragment must either be passed as parameters or declared locally. Forgetting a variable that was mutated in the original fragment breaks the invariant: the caller sees unexpected state after the call. This is the single most common Extract Function bug.
Prior Module Question 4 -- S1: Induction and Behavior Preservation
Proof by induction proves a property holds for every natural number. How is a green test suite between every refactor step analogous, and where does the analogy break?
Answer: Each step is an inductive step: if the system was correct before, and the transformation preserves behavior, the system is still correct. The "base case" is the passing suite at step zero. The analogy breaks where induction is total (infinite domain) and tests are finite -- a green suite proves only that the tested inputs still behave identically. An untested input can still regress. This is why seam/characterization coverage matters before the refactor begins.
Prior Module Question 5 -- S0/S1: Git Commits as History
Why is commit granularity an engineering concern, not a stylistic one, in the context of this module?
Answer: Commits are the atoms git bisect uses to locate regressions. A mixed "refactor + feature" commit forces bisect to land on a multi-cause change where you cannot distinguish the source of a new bug. Commit-per-named-refactor-step is the history you want: bisect lands on one named move, you know exactly which transformation introduced the regression, and you revert a surgical change rather than a blob.
Self-Assessment and Remediation
Mastery Level (90-100% correct, 18-20/20)
- Status: Ready to advance to Module 3 with confidence.
- Evidence: You can choose between named moves for a given smell and defend the choice.
- Action: Proceed to Module 3 (Behavioral Patterns); several patterns there will arrive via the refactors in this module.
Proficient Level (75-89% correct, 15-17/20)
- Status: Good; review the specific concept pages for missed questions.
- Actions:
- For each missed question, revisit the linked concept page.
- Redo two katas from practice page 04 that exercise the weak move.
- Re-attempt the missed questions from memory before moving on.
Developing Level (60-74% correct, 12-14/20)
- Status: Foundational understanding; gaps would hurt in Module 3.
- Actions:
- Redo the full practice sequence 01-04 before retaking.
- Commit at least one real refactor in your own code using the two-hats discipline.
- Pair with a colleague on Kata 5 or Kata 7; these are the most judgment-heavy.
Insufficient Level (<60% correct, <12/20)
- Status: Major gaps; do not advance.
- Actions:
- Return to Module 1 (OOD Foundations & Smells) if you cannot name the smells these moves answer.
- Work through Fowler's opening chapter (book chunks 006-012) end to end with tests.
- Retake the quiz after one full week of inline refactor practice on real code.