books / clean-code / ch-09SHEET 9 / 12 · REV ASIGN IN
MODULE 9 · CLEAN CODE CH 11–12

Growing a System: Construction, Wiring, Emergence

Give construction one address, then let good design emerge from a pass you can actually run.

The idea

When a class writes `new SmtpMailer()` inside its own body, it is doing two jobs: its real work, and deciding which concrete collaborator exists. That second job is why you cannot test it without a mail server, cannot swap the mailer without editing the class, and cannot see the app's shape anywhere. The wiring is smeared across a dozen files.

The fix is to treat startup as its own concern and give it a home. That home is usually main, or one small module main calls. Everything downstream receives its collaborators ready made, through constructors, and never asks where they came from. That is dependency injection, and the demystifying part is that it is not a framework or an annotation. It is a function that calls constructors in the right order and hands the results to each other. You will write one by hand, in about thirty lines.

Once construction lives in one file, that file becomes the schematic. Read it top to bottom and you see every component and every connection. Want a test configuration with an in-memory store, a fixed clock, a silent mailer? That is a second thirty-line function, not a mocking library. Want to time every store call? Wrap the real object in a thin decorator at the wiring point, and the domain code never learns it happened.

The other half of this module is emergence. Kent Beck's four rules, in priority order, tell you whether a design is simple: it runs all the tests, it contains no duplication, it expresses intent, and it minimizes classes and methods. This is not a philosophy, it is a checklist you execute after any feature. Sweep for duplication, rename until the code says what it means, delete what no longer earns its keep, stay green throughout. Good design is what is left over after you run that pass, again and again.

The bench — 4 exercises

EX 01

Gather the news

Move every inline construction out of your domain classes and into one wiring module, so that building the app and using it become separate jobs.

  1. Grep your codebase for every place a domain class instantiates a collaborator inline, plus any singleton accessor like Config.instance(), and list them.
  2. Work leaf-first: take one class that constructs nothing, then push its callers' dependencies up into constructor parameters.
  3. Move each instantiation into a single wiring/main module, threading parameters upward one site at a time.
  4. Kill the singleton last by constructing Config once in main and passing it down.
  5. Re-run your tests after every single move so you are always green-to-green.
hint

Classes that construct nothing are already done — start there and let the changes ripple outward.

hint

Constructing a Date, a Map, or a plain value type inside a method is fine; only project-domain collaborators need to move.

hint

Once everything takes config as a parameter, the singleton has no callers left to hide behind.

DONE WHEN

· All existing tests pass with identical CLI output

· No file outside wiring/ (or tests) instantiates a project-domain class

· Every domain class lists its dependencies in its constructor signature

EX 02

The thirty-line container

Turn your straight-line main into a tiny hand-rolled factory module with a prod assembly and a test assembly, proving DI needs no library.

  1. Create wiring/container with one small builder function per component, each declaring what it needs as parameters.
  2. Add buildProdApp() that assembles the real store, real clock, and real mailer.
  3. Add buildTestApp(overrides) that assembles an in-memory store, a fixed fake clock, and a capturing mailer.
  4. Support per-component overrides with a defaults-plus-spread (or dict.update) one-liner.
  5. Run your whole scenario suite through buildTestApp() and confirm it never touches disk or network.
hint

If your container needs cleverness, it is too big — `buildNotifier(mailer, clock) => new Notifier(mailer, clock)` is the whole trick.

hint

Keep it under about sixty lines; no reflection, no string-keyed registry, no DI package.

DONE WHEN

· buildProdApp() boots and the full suite passes

· buildTestApp() runs the same scenarios entirely in memory, creating no files

· Passing your own fake clock into buildTestApp changes the timestamps in the output

· No DI or IoC package appears in your lockfile or requirements

EX 03

The four-rules sweep

Run Beck's four rules over the whole repo as an explicit, ordered pass, and watch the codebase get physically smaller with behavior unchanged.

  1. Record your baseline: non-blank, non-comment line count of src/, and a green test run.
  2. Rule 2: find duplicated blocks longer than six lines (including mutated copies with renamed variables or reordered args) and extract shared functions.
  3. Rule 3: give every extraction an intent-revealing name; re-read each one and rename until it says what it means.
  4. Rule 4: delete functions nothing calls, and collapse any interface with a single implementation and no test needing a second.
  5. Re-run the tests after each step and compare the final line count against your baseline.
hint

Priority order is the whole rule set: never dedupe in a way that muddies a name — rule 3 outranks rule 4.

hint

If two similar blocks resist a shared name, they may be coincidental duplication that is correct to leave alone.

hint

A simple grep or a small token-hash script over normalized lines is enough to find clones yourself.

DONE WHEN

· Tests green at every step and at the end, with output byte-for-byte unchanged

· No duplicated block longer than six lines remains in src/

· Zero uncalled non-public functions and zero single-implementation speculative interfaces

· src/ line count is lower than the baseline you recorded

EX 04

Cross-cutting without contamination

Add "log every store operation with its duration" by wrapping, not sprinkling — changing only the wiring module and one new file.

  1. Commit or tag your current state so you can diff against it later.
  2. Write a TimingStore decorator with the same interface as your store: it wraps a real store, times each call, and writes to an injected log sink.
  3. Change only buildProdApp() to wrap the store; leave buildTestApp() unwrapped or wrapped with a capturing sink.
  4. Run the prod config and confirm every store operation, including deep internal ones, appears in the timing log exactly once.
  5. Run git diff against your tag and verify no domain file changed.
hint

This is the honest 90% of what AOP frameworks do, in about twenty readable lines.

hint

If wrapping is painful, your store interface is too wide — that is a finding in itself.

DONE WHEN

· Locked stdout is unchanged because timing goes to a separate sink

· git diff touches only the wiring module and the new decorator file

· Every store call is logged exactly once with a duration

Go deeper (after the bench)

Read Clean Code Ch. 11 (pp. 153-166) before the wiring exercises — skim the EJB and AOP history lightly, the load-bearing sections are "Separate Constructing a System from Using It" and "Dependency Injection" — then read all of Ch. 12 (pp. 171-176, six pages) before the four-rules sweep, because those six pages are the exercise. For one free companion, Mark Seemann's "Composition Root" post on blog.ploeh.dk states exercise one's rule precisely: an application gets exactly one place, as close to the entry point as possible, where its modules are composed.