Design principles: SOLID and beyond
Cohesion and coupling as the core idea behind SOLID, DRY, YAGNI, composition and deep modules, and how these principles get misapplied.
13 cards · 8 quiz questions · 10 min read
Software design principles are compressed experience: lessons about what made code painful to change, turned into slogans. Slogans are easy to repeat and easy to misapply. This article explains the best-known principles by tracing them back to the one idea most of them share, and then looks honestly at how they go wrong.
The underlying idea: cohesion and coupling
Two concepts from the structured design work of Larry Constantine and Edward Yourdon in the 1970s still explain most of modern design advice.
- Cohesion is how strongly the contents of a module belong together. High cohesion means a module does one coherent job.
- Coupling is how much one module depends on the details of another. Low coupling means you can change one without editing others.
The goal is for any given change to touch as little code as possible. When related things live together and unrelated things are independent, changes stay local. Nearly every principle below is a more specific way of achieving that.
SOLID, plainly
Robert C. Martin gathered five principles, later nicknamed SOLID, in Agile Software Development, Principles, Patterns, and Practices.
Single Responsibility. A module should have one reason to change. Martin later sharpened this: a module should answer to one actor, meaning one group of stakeholders. If the finance team’s rules and the operations team’s report layout live in the same class, a change for one can break the other. It is not a rule that classes must be tiny.
Open-Closed. Originating with Bertrand Meyer: modules should be open for extension but closed for modification. If adding a new payment method means editing a giant switch in five places, the design is not closed. If it means adding one new class that implements PaymentMethod, it is.
Liskov Substitution. Named after Barbara Liskov: a subtype must work anywhere its parent type is expected. The textbook violation is a Square inheriting from Rectangle, where setting the width silently changes the height and breaks code that assumed rectangles behave independently in each dimension.
Interface Segregation. Clients should not depend on methods they do not use. Several narrow interfaces, such as Readable and Writable, beat one sprawling interface that forces every implementer and caller to care about everything.
Dependency Inversion. High-level policy should not depend on low-level details; both should depend on abstractions. Your billing logic depends on a PaymentGateway interface, and the concrete provider adapter implements it:
interface PaymentGateway {
charge(customerId: string, pence: number): Promise<Receipt>;
}
Now billing can be tested with a fake, and the provider can change without touching business rules.
DRY, YAGNI and KISS
DRY (Don’t Repeat Yourself) comes from Andy Hunt and Dave Thomas’s The Pragmatic Programmer, where it means every piece of knowledge should have a single, authoritative representation. The tax rate, the rule for a valid order, the database schema: define each once. It was never a rule against two lines of code looking similar.
YAGNI (You Aren’t Gonna Need It) comes from Extreme Programming. Do not build for requirements you merely imagine. Speculative flexibility costs time now, adds complexity everyone must read around, and frequently guesses wrong about the future.
KISS (Keep It Simple) asks for the simplest design that meets actual needs. Clever code is a liability when someone else has to debug it at 2am.
Composition over inheritance
The Gang of Four’s Design Patterns (1994) advised favouring object composition over class inheritance. Inheritance is the tightest form of coupling: a subclass depends on its parent’s implementation, so changing the base class can break subclasses in surprising ways, and the relationship is fixed at definition time. Composition builds behaviour from independent objects that talk through interfaces, and pieces can be swapped at runtime. Inheritance still fits genuine “is-a” relationships with stable base classes; it is simply the stronger commitment.
Deep modules
John Ousterhout’s A Philosophy of Software Design offers a complementary lens. The best modules are deep: a simple interface hiding substantial functionality. Unix file I/O is his classic example; a handful of calls like open, read and write hide file systems, caching, and device drivers.
Shallow modules have interfaces nearly as complex as their implementation. A class whose methods mostly pass calls straight through to another class adds names to learn without hiding anything. Ousterhout warns against “classitis”, the belief that more, smaller classes are automatically better design.
How principles get misapplied
Principles are tools for reducing the cost of change. Applied mechanically, they can increase it.
Premature abstraction
Adding interfaces, factories and plugin points for a single implementation, “because Open-Closed says so”, is YAGNI violated in SOLID’s name. Every layer of indirection is something a reader must navigate. Abstractions are cheapest to introduce when you have two or three real cases and can see what actually varies.
The wrong kind of DRY
Two pieces of code that look alike but represent different decisions are not duplication of knowledge. Merging them couples things that should evolve separately. Over time the shared function sprouts boolean flags and special cases for each caller. Sandi Metz put it memorably: “duplication is far cheaper than the wrong abstraction.” When that happens, the fix is often to inline the abstraction back into its callers and start again.
Fragmentation in the name of SRP
Splitting a cohesive algorithm into dozens of one-method classes makes each piece “do one thing” while making the whole impossible to follow. That lowers cohesion; it does not raise it.
Ask of any principle: will this make the changes we actually expect cheaper? If not, it is not helping.
Key takeaways
- High cohesion and low coupling are the goal; most principles are strategies for them.
- SOLID is about managing reasons for change, substitutability and dependency direction.
- DRY is about knowledge, YAGNI about speculation, KISS about needless complexity.
- Prefer composition and deep modules with simple interfaces.
- Principles misapplied produce premature abstraction and wrong abstractions; judge by the cost of real change.
Which pair of ideas underlies most design principles, including SOLID?
Sources
- Robert C. Martin — Agile Software Development, Principles, Patterns, and Practices book The classic presentation of the five SOLID principles.
- John Ousterhout — A Philosophy of Software Design book Deep versus shallow modules, information hiding, and managing complexity.
- Martin Fowler — Refactoring: Improving the Design of Existing Code book Practical view of evolving design incrementally rather than up front.