---
type: convention
title: Sandi Metz Code Philosophy
summary: Distills Sandi Metz's POODR and 99 Bottles principles into actionable rules for OO design. Answers "how should I structure this code, and when is abstraction appropriate?"
tags:
  - type/convention
  - domain/software-design
scope: global
last_updated: 2026-06-27
---

# Sandi Metz Code Philosophy

A philosophy of designing for change, not for cleverness. The goal is code that is easy to change later — which requires keeping objects isolated, messages explicit, and abstractions earned.

## Core Principles

**1. Make the change easy, then make the easy change.**
Before adding behavior, ask whether the current structure makes the change easy. If not, restructure first. This separates the two kinds of work and prevents mixed-purpose commits that are hard to reason about.

**2. Duplication is cheaper than the wrong abstraction.**
Only extract when you have three real, concrete examples and can name the concept precisely. A vague name is a signal the abstraction isn't ready. Wrong abstractions attract more wrong code; tolerate duplication until the pattern is genuinely clear.

**3. Every method/function does one thing.**
If you need "and" to describe what it does, split it. Metz's rule: ≤5 lines in Ruby. Adapted for other languages: ≤10 lines in JS. Single-purpose methods compose cleanly; multi-purpose methods resist change.

**4. Classes have one reason to change (Single Responsibility).**
A class that changes for two different reasons is two responsibilities tangled together. Separate them so a change in one domain can't break an unrelated feature.

**5. Design messages first, classes second.**
Objects are defined by the messages they respond to, not the data they hold. Start by deciding what message you need to send and what you need back — the class structure follows from that.

**6. Open for extension, closed for modification.**
`if`/`switch`/`case` chains that dispatch on type or ID violate this: each new case requires editing existing code. Replace with a registry (plain hash/object) so adding a case is data, not code change.

**7. Depend on abstractions, not concretions (Dependency Injection).**
Inject collaborators explicitly rather than instantiating them inside methods. This makes tests trivial (pass a fake) and keeps modules decoupled from each other's concrete implementations.

## The Enumerated Rules (Sandi Metz's Actual Rules)

These are the four specific limits Metz published — meant to be followed strictly until you fully internalize why:

1. Classes: ≤100 lines
2. Methods: ≤5 lines (Ruby; adapt proportionally to language)
3. Method parameters: ≤4 (and prefer keyword/named args)
4. Controllers: instantiate only one object

Breaking any of these is a smell worth investigating, not a veto — but the burden of proof is on the break.

## Patterns

**Shameless Green first.** When starting a new feature, get to passing tests as fast as possible. Duplication is explicitly acceptable at this stage. Refactor *after* green, not before — premature abstraction when the shape is uncertain locks in the wrong structure.

**Flocking Rules for refactoring.** When pulling duplication into an abstraction: (1) find the most similar things, (2) find the smallest difference, (3) remove only that difference. One transformation at a time; stay green throughout. Big leaps break the test-feedback loop and introduce risk.

**Composition over inheritance.** Prefer `has-a` to `is-a`. Inheritance locks two classes into a hierarchy that's expensive to exit. Composition keeps the relationship explicit and replaceable.

**Law of Demeter: only talk to immediate neighbors.** `a.b.c.d` is a tell — you're coupled to the entire chain. Ask the immediate collaborator for what you need; let it traverse its own graph.

## Anti-Patterns

- **Class or method name has "And" or "Manager"** → multiple responsibilities; split it
- **`obj.foo.bar.baz` chains** → Law of Demeter violation; you're coupled to internal structure
- **`case`/`if` switching on type or id** → Open/Closed violation; use a registry
- **Extracting an abstraction from one or two examples** → wrong abstraction in the making
- **Instantiating dependencies inside methods** → untestable; inject instead
- **Silent catch blocks or swallowed errors** → violates single responsibility of error handling
- **Inheriting from a class just to share code** → composition violation; extract a module/mixin or shared object instead
- **Subtype that changes exceptions or return types** → Liskov Substitution violation; callers can't safely substitute

## Exceptions

**Language/runtime adaptation.** The 5-line rule is for Ruby's expressive syntax. In JS/TS, 10 lines is a reasonable target. In Go with explicit error returns, a stricter reading breaks everything. Apply the *spirit* (single purpose, fits on a screen) proportionally.

**Shameless Green is time-limited.** Duplication is acceptable while finding the shape. Once you have three examples and a name, the refactor is overdue.

**Performance-critical paths.** Clean composition sometimes adds allocation or indirection at hot paths. Profile first; don't preemptively break good design for speculative speed.

**Pragmatic team calibration.** If your team hasn't read POODR, enforce the enumerated rules mechanically first — they create the right pressure without requiring philosophical buy-in upfront.
