Sandi Metz' Rules
slug: sandi-metz-rules
title: "Sandi Metz' Rules"
category: engineering
Sandi Metz' Rules
When to use it: writing or reviewing Ruby/Rails classes, methods, controllers, or
views — or any time a class/method is growing large and you want to keep objects small,
focused, and replaceable. Source: Sandi Metz — Rules / POODR / 99 Bottles of OOP.
Guard-rails that keep objects small, focused, and replaceable. They are defaults, not
dogma — see "Breaking a rule" below.
The four rules
- A class can be no longer than 100 lines of code.
- A method can be no longer than 5 lines of code.
- Pass no more than 4 parameters into a method. Hash options count as parameters — you
can't sneak them in. - Controllers can instantiate only one object. Therefore a view can only know about one
instance variable, and it should send messages to only that object (@thing.parts, not
@thing+@parts+@more).
A corollary that flows from all four: extract a class the moment a method or conditional
starts "knowing too much."
Why each rule exists (so you know when it's safe to bend)
- 5-line methods force you to name intermediate concepts. A 5-line method that needs an
if/elsehas its two branches extracted into methods, each ≤ 5 lines. This is how
polymorphism replaces conditionals. - 100-line classes keep a single responsibility in view. A class crossing 100 lines is
almost always two classes. - 4 parameters signals a missing object. Three+ related params → introduce a parameter
object / value object. - One controller object kills the "instance-variable soup" that couples views to
internals. Wrap the page in a presenter / facade object that exposes exactly what the view
needs.
Breaking a rule
You should break these rules only if you have a good reason or your pair lets you.
In practice: a deviation is allowed when you can state the reason in the code review (or PR
body) in one sentence. "This serializer is 7 lines because splitting it hides the shape of
the JSON" is a reason. "I didn't feel like extracting" is not.
Applying in Rails
- Fat model / fat controller smell → extract service objects (one public
call),
query objects (scoped relations), form/contract objects (validation),
presenters (view-facing). - Replace conditionals with polymorphism: tier classes, state objects, or
caseon a
small enum that delegates. - Value objects for money, scopes, identifiers — small immutable classes, not primitives
passed around. - Controllers: each action loads or builds exactly one object and renders it.
Authorization, fetching, and shaping belong in that object, not the action.
Review checklist
- Any class > 100 LOC? → split by responsibility.
- Any method > 5 lines? → extract named sub-methods.
- Any method/initializer with > 4 params? → introduce a parameter/value object.
- Any controller action with > 1
@ivarreaching the view? → wrap in a presenter. - Any
if/elsifon a type/role/state? → consider polymorphism or a state machine. - Any rule broken without a one-line justification? → fix or justify.
See also
- Scalable Ruby
- Agentic Engineering
- Source: Sandi Metz — Practical Object-Oriented Design in Ruby (POODR), 99 Bottles of OOP