
Lessons from Sandi Metz
Software engineer Sandi Metz writes clear, practical guides to object-oriented programming. In books like Practical Object-Oriented Design in Ruby and 99 Bottles of OOP, she consistently argues for simple, changeable code over clever abstractions. This collection gathers her core advice on testing, refactoring, and knowing when to tolerate duplication.
Part 1: The Cost of Abstraction and Duplication
- On the wrong abstraction: "Duplication is far cheaper than the wrong abstraction." — Source: [SandiMetz.com]
- On managing duplication: "It is cheaper to manage temporary duplication than to recover from incorrect abstractions." — Source: [99 Bottles of OOP]
- On recovering from bad design: "When the abstraction is wrong, the fastest way forward is back." — Source: [SandiMetz.com]
- On fixing a bad abstraction: "Once an abstraction is proved wrong the best strategy is to re-introduce duplication and let it show you what's right." — Source: [SandiMetz.com]
- On premature DRYing: "We are killing ourselves DRYing too early." — Source: [99 Bottles of OOP]
- On discovering abstractions: "Abstractions are found, not created." — Source: [99 Bottles of OOP]
- On committing to code structures: "When you DRY your code, you are making a commitment to an abstraction. If you don't know enough about your domain, then your abstractions are likely incorrect." — Source: [99 Bottles of OOP]
- On acceptable repetition: "If the code is understandable and doesn't change, then duplication costs nothing." — Source: [99 Bottles of OOP]
- On balancing concretions: "Concrete code brings a clear comprehension over what is going on. Abstract code allows us to easily change the behavior. As developers, we must find that balance." — Source: [99 Bottles of OOP]
- On choosing between the two: "Prefer duplication over the wrong abstraction." — Source: [RailsConf 2014]
Part 2: Object-Oriented Design and Messages
- On the true nature of objects: "You don't send messages because you have objects, you have objects because you send messages." — Source: [Practical Object-Oriented Design in Ruby]
- On inheritance: "Inheritance is, at its core, a mechanism for automatic message delegation." — Source: [Practical Object-Oriented Design in Ruby]
- On public interfaces: "Create public methods that allow senders to get what they want without knowing how your class implements its behavior." — Source: [Practical Object-Oriented Design in Ruby]
- On dependency management: "Depend on things that change less often than you do." — Source: [Practical Object-Oriented Design in Ruby]
- On the danger of dependencies: "Every dependency is like a little dot of glue that causes your class to stick to the things it touches." — Source: [Practical Object-Oriented Design in Ruby]
- On technical debt: "Dependencies are killing you. Design might save you." — Source: [Practical Object-Oriented Design in Ruby]
- On behavior over state: "Objects should rely on the behavior of other objects, rather than interrogating their state." — Source: [Practical Object-Oriented Design in Ruby]
- On procedural vs object-oriented mindsets: "Are you going to write procedures or trust objects?" — Source: [RailsConf 2014]
- On coupling: "Code that can tolerate change couples to stability." — Source: [RailsConf 2016]
- On the purpose of design: "The fundamental purpose of design is to allow you to do design later, and its primary goal is to reduce the cost of change." — Source: [Practical Object-Oriented Design in Ruby]
Part 3: The Magic Tricks of Testing
- On the purpose of tests: "Tests are supposed to save us money." — Source: [RailsConf 2013]
- On test absence: "The best kind of test is the one you don't have to write." — Source: [RailsConf 2013]
- On implementation details: "Test the interface, not the implementation." — Source: [RailsConf 2013]
- On the freedom to refactor: "If I test only the interface, it means I can change the implementation without breaking the test." — Source: [RailsConf 2013]
- On private methods: "Don't test private methods." — Source: [RailsConf 2013]
- On internal messages: "Ignore both Queries and Commands sent to self!" — Source: [RailsConf 2013]
- On the emotional arc of testing: "Tests often get worse right before they're about to get better." — Source: [RailsConf 2013]
- On test-driven design constraints: "TDD will punish you if you don't understand design." — Source: [RailsConf 2013]
- On failing tests during refactoring: "If you rearrange code without changing behavior and tests begin to fail, then the tests themselves are flawed." — Source: [99 Bottles of OOP]
- On where abstractions belong: "Tests are not the place for abstractions—they are the place for concretions." — Source: [99 Bottles of OOP]
Part 4: Managing Changeability
- On design vs perfection: "Design is more the art of preserving changeability than it is the act of achieving perfection." — Source: [Practical Object-Oriented Design in Ruby]
- On software lifespan: "Your application needs to work right now just once; it must be easy to change forever." — Source: [Practical Object-Oriented Design in Ruby]
- On undesigned applications: "Successful (working) but undesigned applications carry the seeds of their own destruction; they are easy to write, but gradually become impossible to change." — Source: [Practical Object-Oriented Design in Ruby]
- On future knowledge: "The future is uncertain and you will never know less than you know right now." — Source: [Practical Object-Oriented Design in Ruby]
- On making code work: "Anyone can arrange code to make it work right now." — Source: [Practical Object-Oriented Design in Ruby]
- On code failing: "The first way design fails is due to lack of it." — Source: [Practical Object-Oriented Design in Ruby]
- On the cost of decisions: "Every decision you make includes two costs: one to implement it and another to change it when you discover that you were wrong." — Source: [Practical Object-Oriented Design in Ruby]
- On the single responsibility principle sentence test: "Attempt to describe a class in one sentence. If the simplest description you can devise uses the word 'and,' the class likely has more than one responsibility." — Source: [Practical Object-Oriented Design in Ruby]
- On disjointed responsibilities: "If it uses the word 'or,' then the class has more than one responsibility and they aren't even very related." — Source: [Practical Object-Oriented Design in Ruby]
Part 5: Refactoring Strategy
- On making changes: "Make the change easy (warning: this might be hard), then make the easy change." — Source: [RailsConf 2014]
- On incremental refactoring: "The plan is to nibble away, one code smell at a time, in faith that the path to openness will be revealed." — Source: [99 Bottles of OOP]
- On tolerating ugly code: "Shameless Green requires a tolerance for duplication, an abstention from cleverness, and patience to wait for change." — Source: [99 Bottles of OOP]
- On seeking answers: "You don't know the answer in advance, instead you are seeking it." — Source: [99 Bottles of OOP]
- On intuition in programming: "I'm a big believer in how things feel in programming." — Source: [99 Bottles of OOP]
- On hiding complexity: "Avoid hiding the dirt under the carpet by extracting messy code into private methods with poor names." — Source: [RailsConf 2016]
- On learning the magic tricks: "Good tests aren't magic. They're magic tricks — and you can learn them." — Source: [RailsConf 2013]
- On the programmer's ego: "Abstention from cleverness is a core requirement for writing code that is easy to replace." — Source: [99 Bottles of OOP]
- On discovering paths: "When you are stuck in a bad design, reintroducing the original code often reveals the correct path forward naturally." — Source: [SandiMetz.com]
Part 6: Sandi's Rules for Developers
- On class sizing constraints: "Classes can be no longer than 100 lines of code." — Source: [The Bike Shed]
- On parameter limits: "Pass no more than 4 parameters into a method." — Source: [The Bike Shed]
- On controllers in MVC: "Controllers can instantiate only one object." — Source: [The Bike Shed]
- On view knowledge: "Views can only know about one instance variable." — Source: [The Bike Shed]
- On method brevity: "Methods can be no longer than 5 lines of code." — Source: [The Bike Shed]
- On breaking the rules: "Rule 0: You can break any of these rules if you can convince your pair/reviewer why it's necessary." — Source: [The Bike Shed]
- On applying constraints: "The constraints are not laws, but forcing functions to ensure you consciously decide when complexity is actually warranted." — Source: [Ruby Rogues]
- On enforcing brevity: "Keeping methods under five lines forces you to give names to intermediate concepts, making the code self-documenting." — Source: [Ruby Rogues]
- On hash parameters: "Even when grouping parameters into a hash to bypass the parameter limit, the complexity remains and must be managed." — Source: [The Bike Shed]
Part 7: The TRUE Framework and Code Smells
- On transparency: "The consequences of change should be obvious." — Source: [Practical Object-Oriented Design in Ruby]
- On reasonableness: "The cost of any change should be proportional to the benefits." — Source: [Practical Object-Oriented Design in Ruby]
- On usability: "Existing code should be usable in new and unexpected contexts." — Source: [Practical Object-Oriented Design in Ruby]
- On exemplary code: "The code itself should encourage those who change it to perpetuate these qualities." — Source: [Practical Object-Oriented Design in Ruby]
- On evaluating abstractions: "Concrete code is easy to understand but costly to extend. Abstract code may initially seem more obscure but, once understood, is far easier to change." — Source: [Practical Object-Oriented Design in Ruby]
- On simple behavior: "A class should do the smallest possible useful thing; it should have a single responsibility." — Source: [Practical Object-Oriented Design in Ruby]
- On testing commands: "Assert on the direct public side effect." — Source: [RailsConf 2013]
- On testing queries: "Assert on the result." — Source: [RailsConf 2013]
- On code aesthetics: "The goal is to write cost-effective, maintainable, and pleasing code." — Source: [99 Bottles of OOP]
Part 8: Architecture and Mindset
- On the difficulty of mending: "Making is easy, mending is a challenge." — Source: [Maintainable.fm]
- On mocking limitations: "Only use mocks for outgoing commands (messages that have side effects in other objects)." — Source: [RailsConf 2013]
- On trusting collaborators: "Don't test that an outgoing query works; that is the responsibility of the object receiving the message." — Source: [RailsConf 2013]
- On subjective code quality: "Everybody complains about the weather but nobody does anything about it. Likewise, everyone has an opinion about what good code looks like, but those opinions don't help you create it." — Source: [99 Bottles of OOP]
- On the lifecycle of a codebase: "The original abstraction creates a trap for future developers who feel honor-bound to use it, leading directly to unmaintainable technical debt." — Source: [SandiMetz.com]
- On DRYing lyrics in tests: "DRYing out the lyrics in the test would force you to introduce an abstraction. Abstractions belong in code." — Source: [99 Bottles of OOP]
- On avoiding assumptions: "If you don't know what's going to change, don't guess. Build what you need today and design it so it can change tomorrow." — Source: [Practical Object-Oriented Design in Ruby]
- On developer empathy: "Code is read many more times than it is written, so structure it for the human who comes next." — Source: [Practical Object-Oriented Design in Ruby]
- On novice capabilities: "Novice programmers don't yet have the skills to write simple code." — Source: [Practical Object-Oriented Design in Ruby]