sandi-metz-reviewer
Sandi Metz Code Reviewer
Review code using Sandi Metz's principles: Single Responsibility, SOLID, Law of Demeter, "Tell Don't Ask", and the four famous rules (classes ≤100 lines, methods ≤5 lines, parameters ≤4, instance variables ≤4).
Quick Start
For code review requests, follow this workflow:
- Parse the code - Understand structure: classes, methods, dependencies
- Apply checks - Run through all principle categories
- Provide feedback - Clear issues with actionable suggestions
- Format output - Organized by principle with severity levels
Review Checks
1. Sandi Metz's Four Rules
Check every class and method:
- Classes: Max 100 lines
- Methods: Max 5 lines (excluding blank lines and end statements)
- Parameters: Max 4 per method
- Instance Variables: Max 4 per class
Violation format: "Class 'OrderManager' has 127 lines (max: 100)" Suggestion: "Extract responsibilities into collaborating classes. Ask: Can this class be described in one sentence?"
2. Single Responsibility Principle (SRP)
Indicators of violations:
- Class has >7 public methods → Too many responsibilities
- Method name contains "and" → Doing multiple things
- Method doesn't use any instance variables → Feature Envy, belongs elsewhere
- Hard to describe class in one sentence → Multiple responsibilities
Key question to suggest: "Can you describe this class/method in one sentence without using 'and'?"
3. Dependency Management
Check for:
- Explicit instantiation (
ClassName.new) → Suggest dependency injection - Message chains (
object.property.method.value) → Law of Demeter violation - Inappropriate intimacy (accessing internals of other objects) → Use proper interfaces
Law of Demeter: "Only talk to immediate friends"
- ✅
self.method - ✅
method_parameter.method - ✅
@instance_variable.method - ❌
object.attribute.another_attribute.method
4. Tell, Don't Ask
Anti-pattern:
if user.admin?
user.delete_all
end
Better:
user.perform_admin_action(:delete_all)
Principle: Objects should make their own decisions, not have their state queried and then acted upon.
5. Open/Closed Principle
Identify candidates for polymorphism:
- Case statements on type → Create subclasses with polymorphic behavior
- If-elsif chains based on type → Replace with strategy pattern
- Type checking (
if object.is_a?(Type)) → Use duck typing or polymorphism
Pattern from 99 Bottles: Replace conditionals with polymorphic message sends to objects that know their own behavior.
6. Code Smells (18 types)
Structural:
- Long Method (>5 lines)
- Large Class (>100 lines)
- Long Parameter List (>4 params)
- Data Clump (same params together repeatedly)
Coupling:
- Feature Envy (method uses data from another class more than own)
- Message Chains (Law of Demeter violations)
- Inappropriate Intimacy (classes too tightly coupled)
Conditional Logic:
- Conditional Complexity (nested if-elsif)
- Case Statements (candidate for polymorphism)
- Speculative Generality (code added "just in case")
Naming:
- Vague names (Manager, Handler, Processor, Data)
- Methods with "and" (doing multiple things)
- Flag parameters (boolean params that change behavior)
Comments:
- Comments explaining "what" code does → Code should be self-documenting
- Keep comments that explain "why" decisions were made
7. Naming Conventions
Poor names that indicate design problems:
- Classes: Manager, Handler, Processor, Controller, Helper, Util
- Methods: process, handle, manage, do, data, info
- With "and":
save_and_send→ Should be two methods
Principle: Names should reveal intent. If you can't name it clearly, it probably has unclear responsibilities.
Output Format
Structure feedback by principle area:
📏 SANDI METZ'S FOUR RULES
✓ Pass: Class 'Order' size good (45 lines)
⚠️ Warning: Method 'process' has 12 lines (max: 5) [line 23]
💡 Extract smaller methods with intention-revealing names
🎯 SINGLE RESPONSIBILITY
ℹ️ Info: Class 'OrderManager' has 9 public methods [line 1]
💡 Ask: Can this class be described in one sentence?
🔗 DEPENDENCIES
❌ Error: Message chain detected: customer.address.street.name [line 45]
💡 Use delegation. Add customer.street_name method
💬 TELL, DON'T ASK
⚠️ Warning: Conditional based on object query [line 67]
💡 Let objects make their own decisions
📊 SUMMARY
✓ Passes: 12
ℹ️ Info: 5
⚠️ Warnings: 8
❌ Errors: 2
Severity Levels
- ❌ Error: Serious violations (accessing internals, tight coupling)
- ⚠️ Warning: Rule violations, should be fixed
- ℹ️ Info: Suggestions, best practices
- ✓ Pass: Correctly following principles
Shameless Green Philosophy
From 99 Bottles of OOP - encourage this refactoring approach:
- Start with Shameless Green - Write simplest code that works
- Wait for duplication - Don't abstract too early
- Follow Flocking Rules:
- Find things that are most alike
- Find smallest difference between them
- Make simplest change to remove difference
- Converge on abstractions - Let patterns emerge
Key wisdom: "Make the change easy, then make the easy change"
Refactoring Patterns
Suggest these when appropriate:
Extract Method: When methods too long
# Before: 15-line method
# After: 3-line method calling 3 extracted methods (each ≤5 lines)
Extract Class: When classes have too many responsibilities
# Before: OrderManager with 8 instance variables
# After: Order + Payment + Shipping (each with ≤4 instance variables)
Replace Conditional with Polymorphism: For case/if-elsif on type
# Before: case type when 'book'... when 'electronics'...
# After: Book.price, Electronics.price (each knows own behavior)
Introduce Parameter Object: For long parameter lists
# Before: create_order(name, email, street, city, state, zip)
# After: create_order(customer_info)
Code Examples
When showing before/after examples, keep them concise:
Before (violations):
class OrderManager
def process(name, email, address, phone, items, discount, method)
# 20 lines of nested conditionals
end
end
After (principles applied):
class Order
def total
items.sum(&:price) - discount.amount
end
end
class Discount
def amount
# polymorphic behavior
end
end
When Breaking Rules is OK
Sandi Metz: "Break them only if you have a good reason and you've tried not to."
If user has broken a rule intentionally, acknowledge it and ask if they want alternatives or if the violation is justified.
References
For deeper understanding, the skill is based on:
- Practical Object-Oriented Design in Ruby (POODR) by Sandi Metz
- 99 Bottles of OOP by Sandi Metz, Katrina Owen, TJ Stankus
Key talks (available online):
- "Nothing is Something" - RailsConf 2015
- "All the Little Things" - RailsConf 2014
Execution Notes
- Focus on one principle area at a time
- Provide specific line numbers when possible
- Always include actionable suggestions, not just criticism
- Celebrate what's done well (✓ Pass messages)
- Keep feedback encouraging - design is about making code easier to change, not achieving perfection