SOLIDCONTESTEDPARADIGM-SPECIFICLIFETIME-SPECIFIC

SOLID, Read Honestly

Five heuristics about the cost of change, collected over two decades, of genuinely unequal quality — and none of them a law.

The requirement, the obvious build, and why it breaks

Every lesson starts where the work starts: someone asked for something, and the first implementation that comes to mind survives until the requirement changes.

The question

What do the five principles actually have in common, and how much weight can each one bear?

The requirement

A new team member arrives from a shop where SOLID was enforced in code review, and asks whether this codebase follows it. The honest answer is longer than yes or no, and the team needs a shared one.

The obvious build

Adopt SOLID as the team's design standard, add it to the review checklist, and treat violations as defects. This is attractive for a real reason: it gives a mixed-seniority team a shared vocabulary and something to point at, and having *any* agreed heuristic beats every engineer improvising from taste (A Review Checklist Worth Reading).

Why it breaks

The five differ enormously in precision. LSP has a formal statement with a proof obligation; SRP turns on the word "reason", which nobody has ever defined operationally. Treating them as five equivalent rules gives the vaguest one the same authority as the sharpest.

How it breaks as requirements change
  • The five differ enormously in precision. LSP has a formal statement with a proof obligation; SRP turns on the word "reason", which nobody has ever defined operationally. Treating them as five equivalent rules gives the vaguest one the same authority as the sharpest.
  • Enforced as rules they generate structure that nothing asked for: an interface per class, a layer that only forwards, an abstraction with one implementation (How SOLID Gets Misused).
  • They were formulated for class-based OO with expensive recompilation and shipped binaries. In a codebase built from modules and functions, several of them are either automatic or inapplicable, and forcing the OO ceremony back in is a real cost.
  • A checklist item stops people thinking. The question "what does the next change cost?" gets replaced by "does this satisfy the five?", and those have different answers surprisingly often (Changeability Is the Goal).
RequirementConstraintsInvariantsResponsibilitiesBoundariesInterfacesStateDependenciesFailureImplementationTestsFeedbackEvolution

What limits the solution, and what must never stop being true

This domain leads with these two. A design that ignores its constraints is not a design, and an invariant nobody named is one nothing is protecting.

Constraints
  • Two of the five predate the acronym by a decade and were written about a different language and a different release model.
  • The team ships to production daily; any principle that makes a common change more expensive will be abandoned within a month, correctly.
  • Half the codebase is TypeScript with functions and modules rather than class hierarchies, so advice written for 1990s class-based OO transfers unevenly.
  • There is no empirical study anyone can cite that shows SOLID codebases have fewer defects or cheaper changes. That absence has to be part of the answer rather than glossed over.
Invariants
  • Whatever the team decides, a review comment must be able to name the concrete cost it is preventing. "This violates SRP" is not a review comment; "this class changes for two unrelated reasons and both teams edit it" is.
  • No principle may be applied in a way that makes the changes this system actually receives more expensive.

Who owns what, and where the seams fall

Responsibilities decide boundaries; boundaries decide what an interface has to say.

Responsibilities
  • The team owns deciding which heuristics it uses and saying so; inheriting them unexamined from a book is not a decision.
  • A reviewer invoking a principle owns naming the concrete change it makes cheaper. Without that, the invocation is an appeal to authority.
  • Each principle owns exactly one problem — and knowing which problem is the difference between using it and reciting it.
Boundaries
  • The boundary worth drawing is between the *observation* each principle encodes and the *rule* it was turned into. The observations are mostly sound; the rules are where the damage is.
  • A second boundary: which of the five are about a unit's internals (SRP, ISP), which are about relations between units (DIP, OCP), and which is a correctness property (LSP). They are not the same kind of statement and do not fail the same way.
  • The five are not a system. They were collected, not derived, and they overlap and occasionally pull against each other — ISP pushes interfaces apart, and a strict reading of OCP pushes toward more of them (Over-Decomposition).

Five different claims wearing one acronym

The most useful thing to know about SOLID is that it is not one idea in five parts. It is five separate observations, made by different people, about different problems, with wildly different degrees of precision — and the acronym, coined years after most of them, hides that.

Read the middle column and the letters stop feeling interchangeable. Each one names a real pain that real codebases have. Read the right-hand column and the reason this module exists becomes clear: every one of them has a well-travelled misreading that produces a worse design than doing nothing.

  • What they share is the subject: all five are about the cost of change, and none is about correctness, performance or clarity (The Cost of Change).
  • What they do not share is precision. L is a formal property; S turns on an undefined word; O has been restated so many times that two engineers citing it often mean different things.
  • Three of them (S, I, D) are really about coupling, and the coupling module says the same things with less baggage (Kinds of Coupling).
The problem it namesIts strongest formThe misreading
S — Single ResponsibilityA class that two teams edit for two unrelated reasons is a permanent merge and regression pointGroup things that change together for the same reason; separate things that change for different reasons"One class, one thing" — split until every class has one method and no class does anything (Single Responsibility, Critically)
O — Open/ClosedAdding the fourth variation means editing stable logic that the first three depend onPrefer designs where *common, observed* variation is added without rewriting the stable core"Never modify existing code" — plugin machinery for variation that never arrives (Open/Closed, Critically)
L — Liskov SubstitutionA subtype that breaks caller expectations turns polymorphism into a source of surprise bugsA subtype must honour every promise callers rely on from the supertype — a checkable, formal obligationReduced to the Square/Rectangle puzzle and treated as inheritance trivia rather than a contract property (Liskov Substitution, Critically)
I — Interface SegregationA consumer that depends on a fat interface recompiles and breaks when unrelated parts of it changeConsumers should depend on the smallest contract that serves them"Small interfaces" as a size rule — one-method interfaces everywhere, none of them meaningful (Interface Segregation, Critically)
D — Dependency InversionStable policy that imports volatile detail cannot be tested, reused or changed independentlyPolicy declares the interface it needs; detail implements it — an import-graph fact"Depend on abstractions" — a container, an interface per class, and the same graph as before (Dependency Inversion, Critically)

Where they came from, and why that still matters

SIMPLIFIEDThis timeline compresses a genuinely tangled history. Martin's formulations changed across articles, books and blog posts over twenty years, and the versions people quote are frequently from different decades and are not equivalent — which is a real source of two engineers citing "OCP" at each other while meaning different things. The dates and attributions here are the mainstream account, told at the resolution needed to see that these are five separate artefacts rather than one.

The chronology is not trivia. Two of the five were written about a world where modifying code meant recompiling and re-shipping a binary to customers, and where inheritance was the primary mechanism of reuse. Both of those facts have changed, and the principles that depend on them have aged differently from the ones that do not.

Notice also what the timeline does not contain: a study. There is a substantial body of empirical work on coupling and cohesion metrics generally, and very little that evaluates these five principles as such. Anyone claiming SOLID codebases measurably have fewer defects is going beyond the evidence, in either direction.

A collection, not a theory
11972 Parnas, "On the Criteria To Be Used in Decomposing Systems"
2 -> modules should hide decisions likely to change. The ancestor of S and D.
3
41988 Meyer, Object-Oriented Software Construction
5 -> Open/Closed: extend a *published, compiled* class by inheriting,
6 because modifying it breaks customers who already have the binary.
7
81987 Liskov, "Data Abstraction and Hierarchy" (keynote)
91994 Liskov & Wing, "A Behavioral Notion of Subtyping"
10 -> a formal subtyping obligation, from type theory. Not a style guide.
11
121995-96 Martin, articles in C++ Report
13 -> restates OCP around abstract base classes rather than inheritance;
14 states DIP, ISP (from Xerox consulting work), and SRP.
15
16~2004 Feathers coins the acronym SOLID by reordering the five.
17
182000s- SRP restated repeatedly by its own author: "one thing"
19 -> "one reason to change" -> "one actor / stakeholder".
20 The restatements are evidence the phrasing kept failing in practice.
21
22Missing from this timeline: any controlled study showing that codebases
23following these five have fewer defects or cheaper changes. It may exist
24for coupling and cohesion broadly; it does not exist for SOLID as such.

The 1988 entry is the load-bearing one. Meyer's OCP is a response to a distribution problem — you cannot edit a binary your customers already have — and a service deployed from a monorepo twenty times a day simply does not have that problem. Applying the principle without noticing the context change is how it becomes plugin machinery nobody needed.

A better question to ask in review

The practical replacement for the checklist is not "no principles". It is to name the symptom rather than the letter, because the symptom carries the evidence and the letter does not. A reviewer who says "this class is edited by the billing team and the shipping team for different reasons" has made an argument; one who says "SRP violation" has made an assertion.

The decision below is what that looks like in practice: start from the pain someone actually observed, and let it select the heuristic, rather than starting from the heuristic and going looking for pain.

Someone wants to invoke a principle in review. What is the actual symptom?

What concrete cost has been observed, or is confidently predicted, in this code?

Two teams keep editing this file for unrelated reasons

when Merge conflicts, regressions across features, "why did billing break when we changed shipping?"

cost The SRP conversation, done properly: split by reason to change, and accept a new seam plus the coordination it needs (Single Responsibility, Critically, Divergent Change).

Every new variation edits the same conditional

when The fourth payment method, the fifth export format, all landing in one switch that keeps growing

cost The OCP conversation — but only after the variation is observed. Two cases is not a pattern; the third is evidence (Open/Closed, Critically, The Rule of Three).

A subclass or implementation surprised a caller

when A cache returned stale data where a repository promised current; an implementation threw where callers assumed it could not

cost The LSP conversation, and a contract test. This one is a defect, not a style question (Liskov Substitution, Critically).

Consumers break when unrelated parts of an interface change

when A ten-method port where each consumer uses two, and every consumer recompiles when any method moves

cost The ISP conversation: split the contract by consumer. Costs more types and a naming problem (Interface Segregation, Critically).

Tests need a database, a network or a key

when The module cannot be exercised without infrastructure, so it is the least-tested part of the system

cost The DIP conversation, and usually just injection rather than inversion (Dependency Inversion, Critically, Volatile Dependencies).

No symptom — the structure "does not follow SOLID"

when The code works, changes cheaply, and nobody can name a cost

cost Leave it alone. This is the most common invocation and the one that produces the structure this module warns about (How SOLID Gets Misused).

How to build it

Most important first.

  • Learn the problem each one names, and use the problem as the review vocabulary rather than the acronym.
  • Weight them by precision. LSP is a property a design can actually violate in a checkable way; treat it as close to a rule. SRP and OCP are prompts for a conversation and should never settle one.
  • Ask the domain's own question in place of any of them: what does the next change cost, and which change are we making cheap (The Cost of Change)?
  • Apply them to the parts of the system that are actually changing. A stable module that satisfies none of the five and has not been edited in two years does not need attention (When Design Does Not Pay).
  • Say out loud that they are heuristics. A team that knows it is using a heuristic revisits it; a team that thinks it is using a law defends it.

What the next change costs

The field this whole domain exists for. A structure is only better if it makes the change after this one cheaper — and it is worth saying which changes it does not help.

Cost of the next change
  • Under the checklist reading, the next change costs whatever the accumulated structure charges: more files, more indirection, more places to update for an additive change. The cost is paid on every change, including the ones no principle anticipated.
  • Under the heuristic reading, the next change costs the review conversation about which specific pain applies — a few minutes, occasionally wasted, and it stays proportional to the change.
  • The change that gets most expensive under the checklist reading is the *simple additive one*: adding a field to a type that has been split across an interface, an implementation, a DTO and a mapper costs four edits where it once cost one (Change Amplification).
  • The change that gets cheapest under a judicious reading is the one you predicted correctly, and only that one. This is the honest scope of what any of the five can do.
What the recommended approach costs
  • Treating them as heuristics costs the thing the checklist gave you: a fast, low-conflict way to settle a review disagreement without seniority deciding it. That is a genuine loss and teams feel it.
  • Requiring a named cost per invocation slows reviews and irritates engineers whose instincts are usually right.
  • Discussing which principles apply is itself overhead. On a small, short-lived codebase the correct amount of this discussion is close to zero (When Design Does Not Pay).

What can go wrong

Failure modes
  • The acronym becomes the standard, so a design is defended by which letters it satisfies rather than by what it makes cheap.
  • The vaguest principle does the most work, because "this has more than one responsibility" can be said about almost any class and is difficult to argue against.
  • They get applied to code that has none of the problems they address, producing structure whose only justification is the principle.
  • The mitigation fails too: a team that rejects SOLID wholesale loses the vocabulary and ends up with no shared way to name coupling problems at all, which is worse than the checklist it replaced.
Dependencies, and their direction
  • All five depend on a prior judgement about which changes are likely, and none of them supplies it. That judgement is the actual design work and the principles are downstream of it (Design for the Known, Name What You Assumed).
  • Three of the five (SRP, OCP, DIP) depend on the language having cheap abstraction — interfaces, polymorphic dispatch, separate compilation units. Their cost-benefit shifts sharply where those are expensive.
  • They depend on a release model where modifying published code is risky, which is Meyer's original context and much less true for a service deployed from a monorepo twenty times a day.
Misreads
  • "SOLID is best practice." It is a set of heuristics from a particular OO tradition, with essentially no empirical validation, of unequal quality. Calling it best practice ends the conversation that should be happening.
  • "SOLID is discredited, ignore it." Equally wrong. LSP is a genuinely important correctness property, ISP describes a real coupling cost, and the observation behind SRP is what half this domain is about. Discarding the vocabulary because the checklist was misused is throwing away the useful part.
  • "They are five aspects of one idea." They were collected from different decades, different authors and different problems. LSP is Liskov's, from type theory; OCP is Meyer's, about shipped binaries. The acronym is a mnemonic, not a theory (Patterns as Vocabulary makes the same point about pattern catalogues).
  • "If we followed all five we would have a good design." You would have a particular kind of structure. Whether it is good depends entirely on which changes arrive, and no principle in the list knows that (Speculative Generality).
Smells this explains
  • speculative-generality
  • shotgun-surgery

Testing it, and how it ages

What to test, and at which boundary
  • The useful test of any structural principle is a change-cost test, not a conformance test: take a change the team actually received last quarter and ask what it touched (Change Amplification).
  • For LSP specifically there is a real test — a contract test every implementation must pass — which is a good indication of how much sharper it is than the other four (Contract Tests).
  • Architecture tests can enforce dependency direction mechanically, which is the one thing in this area a machine can check reliably (Dependency Direction).
  • Do not write conformance tests for the other principles. There is no test for "one reason to change", which is itself informative.
How this design ages
  • The principles have already evolved once: Martin's SRP was restated over the years from "one thing" toward "one reason to change" and then toward "one actor or stakeholder", precisely because the earlier phrasings did not survive contact with real code.
  • OCP as Meyer stated it in 1988 is largely obsolete in its original form — it addressed the risk of modifying already-compiled, already-shipped code, which continuous deployment from a single repository changes fundamentally.
  • What has aged best is the underlying question all five are circling: which changes should be local, and what structure makes them so. That question long predates SOLID and will outlast it (What Makes Software Hard to Change).

Where this applies

This domain's advice is contested more than most. These labels say what each claim is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view rather than a caricature.

  • CONTESTEDThe strongest case *for* teaching SOLID as a standard, and it is better than its critics allow: a mixed-ability team needs a shared, teachable vocabulary more than it needs each engineer's independent judgement, and a mediocre rule applied consistently beats good judgement applied unevenly. Real codebases are more often damaged by nobody having any structural vocabulary than by having a slightly wrong one, and the principles at least point at real phenomena — coupling, change locality, substitutability. The rebuttal is not that this is wrong but that the consistency benefit is available from any agreed heuristic set, including one written for the actual codebase, and the specific five carry OO assumptions that a modern polyglot team pays for.
  • PARADIGM-SPECIFICAll five were formulated for class-based OO with inheritance, interfaces and separate compilation. In a functional codebase, SRP is largely what a function already is, OCP is achieved by passing a function rather than by subclassing, LSP becomes ordinary parametricity and type-class law-abidance, ISP is the observation that a function should take the narrowest argument type, and DIP is a function parameter. The underlying concerns survive; the mechanisms and most of the ceremony do not, and importing the ceremony is a common and expensive mistake in mixed codebases.
  • LIFETIME-SPECIFICFor a service with a known two-year life and one team, the accumulated structure of a strict reading never repays, because the third and fourth variations it was built for never arrive. For a codebase heading into its second decade with turnover, the same structure is what lets a stranger change one thing without reading everything. The principles do not distinguish these cases and the team must.

Where the depth lives

This domain teaches the codebase-level structure and hands the rest off.

Domains that do not exist yet
  • Programming Languages & Runtime Internals — how much of SOLID a language gives you for free, and how much it charges for, differs enough between class-based OO, ML-family functional and trait-based systems languages that the same principle is trivial in one and expensive in another.
  • Testing & Reliability Engineering — the only one of the five with a mechanical check is LSP, via a shared contract suite, and designing that suite is theirs.