Skip to content

Simplifying Conditionals

The simplifying conditionals family is about decision logic: the branches, guards, and switches that decide what the program does next. A condition that started as a single clear question grows extra clauses, nests inside other conditions, and sprouts copies across the codebase. These refactorings untangle that growth so the reader can see, at a glance, which case leads where.

Complex conditional logic is one of the loudest signals that a function is hard to maintain. The warning signs are familiar: a boolean expression so long you have to read it twice, an arrow of nested if blocks marching off the right edge of the screen, a switch on a “type” field repeated in five functions, and if (x == null) scattered through every method that touches x. Each of these has a named cure, and each cure leaves the behaviour identical while making the intent obvious.

SymptomWhat you noticeMove
Complex conditionalA boolean expression you must read twice to decodeDecompose Conditional
Duplicated conditionalSeveral checks that all lead to the same resultConsolidate Conditional Expression
Nested conditionalif/else nested so deep the happy path is hiddenReplace Nested Conditional with Guard Clauses
Repeated Switches (Fowler, ch. 3)The same type switch repeated in several functionsReplace Conditional with Polymorphism
Null checkThe same if (x == null) in every method that touches xIntroduce Special Case
Control flagA boolean that exists only to steer a loopReplace Control Flag with Break/Return
Error codeA sentinel value for failure that is easy to ignoreReplace Error Code with Exception
flowchart TD
  A["Tangled conditional logic"] --> B{"What is wrong?"}
  B -->|"A condition is hard to read"| C["Decompose Conditional"]
  B -->|"Many checks, one outcome"| D["Consolidate Conditional Expression"]
  B -->|"Deep nesting hides the happy path"| E["Replace Nested Conditional with Guard Clauses"]
  B -->|"A switch on a type keeps recurring"| F["Replace Conditional with Polymorphism"]
  B -->|"The same null check is everywhere"| G["Introduce Special Case"]
  C --> H["Decision logic that explains itself"]
  D --> H
  E --> H
  F --> H
  G --> H
Choosing a simplifying-conditionals move by what is wrong

Eight refactorings, each small and reversible:

  • Decompose Conditional — extract the test, the then-branch, and the else-branch into well-named functions so the structure reads like a sentence: if it is a peak day, charge the peak rate, otherwise the regular rate.
  • Consolidate Conditional Expression — when several separate checks all lead to the same result, combine them into one condition and name it, so the reader learns why rather than how many ways.
  • Replace Nested Conditional with Guard Clauses — flatten deep if/else by returning early for the special cases, leaving the main path unindented and obvious.
  • Replace Conditional with Polymorphism — when a switch or if on a type recurs, push each branch into a subclass method so the language dispatches for you.
  • Introduce Special Case — replace a repeated null-or-special check with an object that responds with sensible defaults, so callers stop asking “is it missing?”.
  • Introduce Assertion — make an assumption a section of code silently relies on explicit, so a violated assumption fails loudly at its source instead of corrupting data downstream.
  • Replace Control Flag with Break/Return — delete a boolean that only exists to steer a loop and let break, return, or continue express the exit where it actually happens.
  • Replace Error Code with Exception — stop reporting failure with an ignorable sentinel; raise an exception, or in Go and Rust return an explicit error/Result, so failure cannot be mistaken for success.

Each lesson shows the same before-and-after in TypeScript, Python, Go, and Rust, then walks through the safe, ordered mechanics — running your tests between every step.

What do the "simplifying conditionals" refactorings primarily improve?
Which refactoring flattens deep if/else by returning early for special cases?
When several checks all lead to the same result, which move combines them?