books / clean-code / ch-02SHEET 2 / 12 · REV ASIGN IN
MODULE 2 · CLEAN CODE CH 3

Functions: Small, Then Smaller Still

Take one 200-line monster apart into a cascade of ten-line functions, and prove its behavior never changed by a single byte.

The idea

Every real codebase has one: a function that started at twenty lines and now runs two hundred, five levels of nesting deep, with a few boolean parameters steering it down different paths. Nobody planned it. It grew one "just add an if" at a time. This module hands you exactly that animal, and it works. Your job is not to fix a bug. Your job is to make it readable without changing what it does.

The core claim is almost embarrassingly simple: functions should be small, and each should do one thing. The test for one thing is whether you can pull another function out of it with a name that isn't just a restatement of its code. Small functions aren't a style preference. They are what lets the name do the work, so a six-line function called addSepiaFilter needs no comment and no mental simulation.

A function should also speak at a single altitude. Either it orchestrates, or it does bit-work like multiplying an amount by a tax rate, but never both in the same body. Mixing altitudes is what makes long functions exhausting. From that follows the stepdown rule: arrange the file so it reads top-down like a newspaper, each function followed by the functions one level of detail below it.

Arguments are cost. Every parameter is something the reader holds in their head and every test must vary. Worst of all is the flag argument, render(doc, true), which is a confession that the function does two things and the caller picks. The fix is always two functions with honest names. Do all of this with the tests running after every single extraction, and refactoring stops being scary.

The bench — 4 exercises

EX 01

The God-Function Teardown

Decompose a 200-line generateReport() into small, single-purpose functions using repeated Extract Function, keeping tests green at every step. This is where small-and-one-thing stops being advice and becomes muscle memory.

  1. Write or run a characterization test suite that pins the current output for a dozen inputs, including empty orders, draft mode, and locale-formatted totals
  2. Start with the innermost nested block: extract it into a named function, run the tests, commit
  3. Repeat until no block is extractable, naming each function for what it does before looking at how
  4. Replace each boolean parameter with a pair of honestly named functions, or let the structure absorb it
  5. Bundle argument clumps that always travel together into a single params object
hint

Extract the deepest nesting first; the levels above tend to collapse on their own.

hint

If an extraction's name needs an "and" in it, you extracted two things.

hint

Never rewrite from scratch. If your tests can't tell the difference, they aren't pinning enough.

DONE WHEN

· Test suite is green after every individual extraction, not just at the end

· No function exceeds 20 lines and no body nests more than 2 levels deep

· No function takes more than 3 arguments and no boolean parameters remain

· generateReport still exists as the public entry point with an unchanged call signature

EX 02

Flag-Argument Purge

Hunt down every call site that passes a boolean literal and split the callee into honestly named functions. You will feel how much a flag was hiding.

  1. Grep the source for calls containing true or false as an argument and list every file and line
  2. For each flagged function, split it into two named functions such as saveAndNotify and saveSilently
  3. Update every call site to the new names, one function at a time with tests in between
  4. Where a boolean glued two unrelated behaviors together, untangle them rather than mirroring the old shape
hint

If the two new functions share most of their body, extract that shared part into a third private function both call. That's the pattern, not a smell.

hint

Names from module 1 still apply: the split names must reveal intent, not just say WithFlag and WithoutFlag.

DONE WHEN

· A grep for boolean literals passed as arguments returns nothing in the module source

· Every new function name reads as a full sentence at the call site

· The full test suite passes unchanged

EX 03

The Stepdown Rewrite

Reorder a file of already-small functions so it reads top-down like a newspaper. Pure movement, no logic edits, which makes the ordering idea impossible to fake.

  1. Pick a file whose functions are correct but scattered, and sketch its call graph on paper
  2. Move the public entry point to the top of the file
  3. Walk the call graph depth-first from that entry point and place each function in the order it is first called
  4. Verify with a diff that only whole functions moved and no line inside any body changed
hint

Depth-first from the entry point produces a valid stepdown order automatically.

hint

For sibling functions, keep them in the order their caller invokes them.

DONE WHEN

· For every call inside the file, the caller's definition appears above the callee's

· The diff shows moved blocks only, with no edits inside any function body

· Tests pass without modification

EX 04

Bury the Switch

Collapse the same switch over an output format, duplicated across header, body, and footer rendering, into one creation point with polymorphic formatters. Optional, and the bridge to design patterns later.

  1. Find every switch or if-chain over the format value and note what each branch does
  2. Define one formatter interface with the methods the render code actually needs
  3. Write one formatter per format, then a single factory function that maps a format name to a formatter
  4. Replace all three switches with calls to the formatter object, leaving the factory as the only place format names appear
hint

A switch is tolerable exactly once, at the bottom of the abstraction, where instances are made.

hint

Prove the structure by adding a fourth format like markdown; it should cost one new file and one factory line.

DONE WHEN

· Exactly one switch over the format value remains, and it lives in the factory

· Adding a new format touches only a new formatter plus one line in the factory

· Output for all existing formats is byte-for-byte identical to before

Go deeper (after the bench)

Read Clean Code chapter 3 (Functions, pp. 31-52) now that you've done the teardown; the testableHtml worked example is exercise 1 in miniature, and the switch-statement section reads very differently after exercise 4. For the mechanics of the move you just performed twenty-five times, plus the cases where you shouldn't, see Martin Fowler's free Refactoring catalog entry for Extract Function at refactoring.com/catalog.