Skip to content

Decompose Conditional

Take a conditional whose test and branches are each a clump of detail, and replace each clump with a call to a function named after its intent. The if then reads as a sentence — if it is not summer, use the winter charge, otherwise the summer charge — and the messy arithmetic moves out of the way.

This is the cure for a complicated conditional — the kind where you cannot tell, without parsing the expression, why a branch is taken. The test is a tangle of date comparisons; the branches are formulas with magic numbers. The control flow you care about (which case applies) is drowned in the computation. When the condition and each branch carry their own name, the decision becomes obvious and the math is tucked into helpers you can read on demand.

A billing function charges a winter rate outside summer and a summer rate inside it. Before, the test and both branches are inline arithmetic. After, each part is a named helper.

// Before
function charge(date: Date, quantity: number): number {
let result: number;
if (date.getMonth() < 5 || date.getMonth() > 8) {
result = quantity * 12 + 100;
} else {
result = quantity * 8;
}
return result;
}
// After
function charge(date: Date, quantity: number): number {
return notSummer(date) ? winterCharge(quantity) : summerCharge(quantity);
}
function notSummer(date: Date): boolean {
return date.getMonth() < 5 || date.getMonth() > 8;
}
function winterCharge(quantity: number): number {
return quantity * 12 + 100;
}
function summerCharge(quantity: number): number {
return quantity * 8;
}
flowchart LR
  subgraph Before["Before"]
    A["if (date before summer<br/>or date after summer)<br/>charge = qty * winterRate + flat<br/>else<br/>charge = qty * summerRate"]
  end
  subgraph After["After"]
    B["if notSummer(date)"]
    B --> C["winterCharge(qty)"]
    B --> D["summerCharge(qty)"]
  end
  Before -.->|"Decompose Conditional"| After
A tangled if becomes a named test plus two named branches
  1. Apply Extract Function to the condition: lift the whole boolean test into a function whose name answers why this branch is taken (notSummer), not how it is computed.
  2. Apply Extract Function to the then-branch. Name it after the result it produces (winterCharge).
  3. Apply Extract Function to the else-branch in the same way (summerCharge).
  4. Run your tests after each extraction. Behaviour must be unchanged.
  5. With all three parts named, the original if is short enough to leave as-is or to collapse into a single expression if your language has a clean conditional expression.

Reach for Decompose Conditional whenever the test of an if is hard to read, or whenever a branch is a block of computation that obscures the decision being made. It pairs naturally with Extract Function, which is the tool it leans on three times.

The cost is the same as any extraction: three short hops from the if to its helpers. That is almost always worth paying, because a reader scanning for control flow can now read the decision without diving into the arithmetic. If a branch is already a single trivial expression, leave it inline — naming quantity * 8 adds nothing.

Which three parts of a conditional does Decompose Conditional extract?
What should the extracted condition function be named after?
Which existing refactoring does Decompose Conditional rely on?
When should you leave a branch inline instead of extracting it?