books / clean-code / ch-08SHEET 8 / 12 · REV ASIGN IN
MODULE 8 · CLEAN CODE CH 10

Classes That Hold Together

A hundred clean methods in one class is still a junk drawer — the next unit up from the function has its own version of doing one thing.

The idea

By now the inside of your code is fine: names say what they mean, functions do one thing, tests hold the net. But open the 700-line ReportManager and it still fills you with dread. Functions have a container problem, and the container is the class.

The rule is not a line limit. A class should have one reason to change. ReportManager loads data, formats output, sends email, and reads config — four jobs with four different people who could each demand a change tomorrow, all sharing fields and blast radius.

Cohesion is how you find the seams. Look at which methods touch which fields. In a healthy class most methods use most fields; in a God-class you find clusters — the email methods only touch smtpHost and retryCount, the formatting methods only touch template and locale. Each cluster is a class trying to get out.

Splitting does not multiply complexity, it files it. And the payoff is testable: with a good split, an anticipated change should land as a new class plus one wiring line, and a class that depends on a Clock interface instead of the system clock can be tested with a fake. This module makes you cash that bet rather than believe it.

The bench — 3 exercises

EX 01

God-Class Fission

Split the 700-line ReportManager along its real field-usage seams instead of by vibes, so the boundaries come from cohesion rather than intuition.

  1. Build a field-usage table by hand: list every method of the class down one side, its 14 fields across the top, and mark which fields each method reads or writes.
  2. Circle the clusters that emerge — expect roughly data access, formatting, delivery, and config.
  3. Extract one cluster at a time into its own class, moving its fields with it, running the test suite after each extraction and committing while green.
  4. Shrink ReportManager to a thin coordinator that wires the pieces together, or delete it entirely.
  5. Re-run your field-usage table per new class and check each one's methods touch most of its own fields.
hint

If a method reads template and also hits the database, that is the real finding — it does two things. Split the method first, then file each half.

hint

Never move two clusters in one commit; green-to-green steps are what make this safe.

DONE WHEN

· Full test suite passes at every commit, not just the last one

· ReportManager's old responsibilities live in at least four separate classes

· No resulting class exceeds ~100 lines

· In each new class, methods on average use more than half its fields

EX 02

The Change Probe

Take a surprise feature request — reports must also export as JSON — and see how many existing files you are forced to open. That count is the honest grade on your split.

  1. Commit your Module 8 work so you have a clean baseline to diff against.
  2. Add JSON export by writing one new formatter class implementing the same interface as the existing formatter.
  3. Register the new format wherever formats are wired up, and write tests for the JSON output.
  4. Run git diff against the baseline and count how many pre-existing files you modified, and how many lines in each.
  5. If you touched more than the wiring, move the seam and try the feature again from the baseline.
hint

If JSON needs data the formatter interface does not expose, do not widen every formatter — reshape the interface around what any format needs: report data in, bytes out.

hint

Needing to edit the CSV formatter is a signal about your split, not a failure of the exercise.

DONE WHEN

· JSON export works and its tests pass with the rest of the suite green

· The diff adds new files and modifies at most one existing file

· That one modification is only in the registration or wiring section, roughly three lines or fewer

EX 03

Cut the Concrete

Replace direct calls to the system clock and filesystem with tiny interfaces you own, then write three tests that were impossible before.

  1. Find every place a domain class calls the system clock or touches files directly.
  2. Define two minimal interfaces you own — Clock with a single now method, and FileStore with read and write — and make the real implementations trivial adapters.
  3. Inject both at construction so no domain class constructs its own infrastructure.
  4. Write three new tests: an overdue-report test with a frozen fake clock, a last-day-of-February boundary test, and a formatter test against an in-memory FileStore.
  5. Time the three tests and grep the domain code for clock and filesystem imports.
hint

Keep the interfaces selfishly small. Clock has one method because your code needs one; fat interfaces are God-classes in disguise.

hint

Concrete infrastructure should appear only in the adapter files and wherever you wire the app together.

DONE WHEN

· No domain class imports the filesystem or reads the system clock directly

· The three new tests pass and finish in well under a second combined — no temp files, no sleeps

· Swapping in the fake Clock requires no change to the classes under test

Go deeper (after the bench)

Read Clean Code chapter 10 now — the SuperDashboard and Sql worked examples show the fission process on real code, and the "Organizing for Change" section is exercise two's argument in Martin's own words. Then read Martin's later free essay "The Single Responsibility Principle" on blog.cleancoder.com, which sharpens SRP into a question about people: a class should be responsible to exactly one stakeholder.