Simplifying Conditionals
Intent
Section titled “Intent”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.
The smell
Section titled “The smell”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.
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 What this module covers
Section titled “What this module covers”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/elseby returning early for the special cases, leaving the main path unindented and obvious. - Replace Conditional with Polymorphism — when a
switchorifon 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, orcontinueexpress 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.