skip to content

Refactoring Catalog Techniques

The named mechanical moves — extract function, extract class, move method, introduce parameter object, replace conditional with polymorphism, replace nested conditionals with guard clauses. Knowing them by name lets you describe a change precisely and lets your IDE do most of the work safely.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

6

What is the Extract Function refactoring (also called Extract Method), what are its mechanical steps, and what signals tell you a fragment is worth extracting?

level: juniorimportance: must knowfreq 78%

answer

  1. Name the intent, not the implementation
  2. Reads → parameters, writes → return
  3. Comment you'd write = function name
  4. Inline Function is the inverse
  5. Watch early returns and try/finally

basics

~20 s

Take a chunk of code inside a function, move it into a new function whose name says what it does, and call that new function from the original spot. Behaviour stays identical; only the structure changes.

solid answer

~50 s

Extract Function moves a coherent fragment of a function body into its own named function and replaces the fragment with a call. Mechanics: name the new function after its intent, not its implementation; copy the fragment out; every local variable the fragment reads becomes a parameter; a variable the fragment writes that is still needed afterwards becomes the return value; if there are several such written variables, that is a signal to slice the extraction differently or return a small result object; replace the fragment with the call; run the tests; then look for duplicate fragments elsewhere and point them at the new function too. The trigger is not line count but the gap between what the code says and what it means: if you would write a comment above a block to explain it, that comment is a function name waiting to happen. Extract Function is the workhorse used inside most other catalog refactorings.

code

pseudocode · 14 lines
pseudocode
// before
function printOwing(invoice) {
  let outstanding = 0
  for (o in invoice.orders) outstanding += o.amount
  // print details
  print("name: " + invoice.customer)
  print("amount: " + outstanding)
}

// after: the comment became a function name
function printOwing(invoice) {
  const outstanding = calculateOutstanding(invoice)   // reads -> parameter, write -> return
  printDetails(invoice.customer, outstanding)
}

go deeper

for a junior

Define it plainly (move a block into its own named function and call it), and mention that tests must stay green because behaviour must not change.

for a middle

Give the mechanics precisely — reads become parameters, writes become return values — and name the trigger as "a comment you were about to write" rather than line count.

for a senior

Discuss edge cases (early returns, try/finally, multiple written variables), the Inline Function inverse and the inline-then-re-extract pass, and how Extract Function underpins Decompose Conditional, Replace Temp with Query and Extract Class.

for a principal

Frame it as the atomic step that makes large restructurings safe and reviewable: small behaviour-preserving commits, tests green throughout, tool-assisted where possible, and separated from behaviour-changing commits so reviewers and bisects can tell them apart.

### First, what "refactoring" means A **refactoring** is a change to the internal structure of code that makes it easier to understand and cheaper to change, **without altering externally observable behaviour** — same outputs for the same inputs, same side effects, same errors. Named catalogs (most famously Martin Fowler's *Refactoring*) give each transformation a **name**, a **motivation**, and **mechanics**: a numbered recipe of tiny steps, each small enough that you can run the tests after it and know exactly what broke. ### Extract Function **Motivation.** A function is doing several things at different levels of abstraction. Reading it forces you to reconstruct intent from implementation details. Extracting a fragment into a well-named function replaces "how" with "what" at the call site, and the details drop one level down where you only look if you care. **Mechanics (behaviour-preserving, one small step at a time):** 1. Create a new empty function. Name it after **what it does**, not how — `isEligibleForDiscount()`, not `checkFlagsAndDates()`. If you cannot name it, that is evidence the fragment is not actually a coherent idea; adjust the boundaries. 2. Copy the fragment into the new function's body. 3. **Read set → parameters.** Any local variable the fragment reads but does not declare becomes a parameter. 4. **Write set → return value.** Any variable assigned inside the fragment and still used after it must come back. If exactly one, return it. If two or more, either extract a different slice, extract twice, or return a small value object (which is itself the *Introduce Parameter Object* / result-object move). 5. Replace the original fragment with a call to the new function. 6. Compile and run tests. 7. Scan for other code that does the same thing and replace it with a call — extraction is how duplication gets removed. ### Signals that a fragment should be extracted - You were about to write an **explanatory comment** above it. - The fragment sits at a **different level of abstraction** than its neighbours (low-level string fiddling next to high-level workflow steps). - It is **duplicated** — even approximately. - It is **conditionally guarded** by a complex boolean you would like to name. - You want to **test it** or **reuse it** independently. Length is a weak signal. A one-line extraction is perfectly legitimate when the name adds meaning: replacing `if (d.status == 3 && d.exp > now)` with `if (isActive(d))` is a real gain. Conversely a thirty-line function that reads as one coherent step may be fine. ### The inverse: Inline Function **Inline Function** is the mirror image: replace a call with the body of the called function and delete the function. Motivation: the body is as clear as the name (a function whose whole body is `return this.rating` is noise); or the codebase has accumulated a thicket of badly-factored small functions and you want to inline them all back together before re-extracting along better seams. That "inline then re-extract" pass is a standard technique — you do not have to get the decomposition right in one move. **Inline mechanics:** verify the function is not polymorphic (an overridden method cannot be safely inlined, because the call site may dispatch to a subclass); find every caller; replace each call with the body, adapting names; run tests after each; delete the function. ### Edge cases and hazards - **Side effects.** If the fragment mutates shared state, reads a clock, or performs I/O, extraction is still behaviour-preserving as long as you do not change *when* it runs. Do not "helpfully" move the call earlier or later. - **Early return / break / continue** inside the fragment. A `return` in the middle of an extracted block cannot simply move — the extracted function returning does not return from the caller. You must convert it to a returned value the caller acts on, or choose a different boundary. - **Exceptions** propagate identically, so they are usually safe, but a `try/finally` split across the boundary is not. - **Overload of parameters.** If the extraction needs six parameters, the seam is wrong, or the data belongs together (see Introduce Parameter Object), or the function belongs on another object (see Move Function). ### Why it matters beyond tidiness Extract Function is the primitive that other refactorings are built from: *Decompose Conditional* is Extract Function applied to condition and branches; *Replace Temp with Query* is Extract Function applied to the expression assigned to a temp; *Extract Class* is a sequence of extractions followed by moves. Interviewers ask about it because fluency here predicts whether someone can make a large restructuring safely in small steps rather than a risky rewrite.

  • When would you apply Inline Function instead?
    When the body is at least as clear as the name (a delegating one-liner), or when a group of small functions has been factored along the wrong seams — inline them all back into one place, then re-extract along better boundaries. Do not inline a method that is overridden by subclasses, because call sites dispatch polymorphically.
  • The fragment you want to extract assigns three local variables that are used afterwards. What do you do?
    Do not return a tuple of three loose values as a reflex. Either pick a different extraction boundary, extract three smaller functions (often each becoming a query), or introduce a small named result object if the three values genuinely belong together as a concept.

Like adding headings and sub-sections to a wall of prose. The words do not change, but a reader can now skim the headings and dive into only the section they care about.

saying these in an interview costs you the question

  • "Extract anything longer than N lines" — line count is a proxy, not the criterion; the criterion is a coherent, nameable idea at a consistent level of abstraction.
  • Naming the extracted function after its implementation (`loopOverOrdersAndSum`) rather than its intent (`calculateOutstanding`).
  • Claiming extraction improves runtime performance — it is behaviour-preserving structural change; performance is at best neutral and should be measured, not assumed.
  • Extracting a fragment containing a mid-block `return` and assuming the caller still returns early.
  • Treating refactoring as "cleanup done later in a separate ticket" rather than continuous small steps taken with tests green.

context

open as a page

Explain the refactorings "Replace Nested Conditional with Guard Clauses" and "Decompose Conditional". How do they differ, and when do you reach for each?

level: middleimportance: must knowfreq 62%

basics

~20 s

Guard clauses flatten deeply nested if/else by returning early for the unusual or invalid cases first, leaving the normal path unindented at the end. Decompose Conditional instead replaces a complicated condition and its branch bodies with well-named function calls.

open as a page

What is the "Introduce Parameter Object" refactoring, what smell does it address, and what does it enable beyond a shorter parameter list?

level: middleimportance: should knowfreq 48%

basics

~10 s

When the same group of arguments keeps travelling together through many functions, bundle them into one small object and pass that instead. The parameter lists shrink and the group finally has a name.

open as a page

What is the "Replace Temp with Query" refactoring, what does it unlock, and when is it the wrong move?

level: middleimportance: should knowfreq 42%

basics

~20 s

Replace a local variable holding the result of an expression with a small function that computes that expression, then call the function wherever the variable was used. Fewer locals makes the surrounding code easier to extract into other functions.

open as a page

When should you apply "Replace Conditional with Polymorphism", how do you do it in safe steps, and when is it the wrong call?

level: seniorimportance: should knowfreq 55%

basics

~20 s

When the same switch on a type code appears in several places, create one subclass (or strategy object) per case, move each branch's body into the matching subclass, and let the language dispatch. Adding a new case then means adding a class instead of editing every switch.

open as a page

How do you carry out Extract Class, Move Function and Move Field across a large, actively-developed codebase without a risky big-bang change — and how do you know the extraction boundary is right?

level: principalimportance: nice to knowfreq 32%

basics

~20 s

Do it in small, always-green steps: create the new class, move one field or method at a time while the old class delegates to it, run the tests each time, and only remove the delegation once every caller has migrated. Never move everything at once.

open as a page