Code That Explains Itself: What SOLID Design and Docstrings Reveal About Software That Lasts
Hatched by Kai Nguyen
Jun 29, 2026
10 min read
2 views
89%
The Hidden Problem: Good Code Is Not Just Correct Code
Most programmers think software quality begins with correctness. The code runs, the tests pass, and the feature ships. But the harder question is this: will someone else, including future you, still understand and safely change it six months from now? That is where code stops being a mechanical artifact and becomes a communication medium.
Two ideas that often get treated as separate, one about class design and one about documentation, are really solving the same problem from different angles. SOLID principles try to make the structure of code intelligible. Docstring conventions try to make the behavior and intent of code intelligible. One shapes the architecture of meaning. The other labels that architecture so humans can navigate it.
That is the deeper tension in software design: should code speak for itself, or should we teach it to speak clearly? The best answer is neither extreme. The best code is designed so that its structure reduces confusion, and documented so that its purpose is unmistakable where structure alone cannot carry the burden.
Structure and Explanation Are Not Competing Goods
There is a common misconception that documentation is a crutch for bad design. If the code is clean enough, so the thinking goes, comments and docstrings are unnecessary. But this view confuses two different kinds of clarity.
Design clarity comes from how responsibilities are divided. A class that does one thing, depends on abstractions, and is easy to replace has an architecture that signals its role. Narrative clarity comes from prose that tells you what a function, class, module, or attribute is for, what it expects, and what it promises. SOLID principles improve the former. Docstrings improve the latter.
Think of a building. SOLID is the floor plan, the load-bearing structure, the logical separation of rooms. Docstrings are the signs on the doors, the placards explaining which room is the archive, which switch controls the lights, and which door stays locked for safety. A great building needs both. A beautiful floor plan without signage still confuses visitors. Perfect signs in a badly designed building merely help you get lost more efficiently.
Good software is not only organized. It is legible at multiple distances.
That phrase matters because readers approach code at different scales. Sometimes they need the whole architecture. Sometimes they only need to know what one method accepts. Sometimes they just need to understand why a strange edge case exists. No single technique solves all three. SOLID reduces the amount of explanation needed. Docstrings supply the explanation that remains necessary.
SOLID Principles: The Art of Making Meaning Local
The deepest value of SOLID is not elegance for its own sake. It is locality of reasoning. When responsibilities are separated well, you can understand one part of a system without mentally simulating the whole thing. That is a profound cognitive gift.
A class with a single responsibility does not force you to ask whether a change in database behavior might break email formatting or whether a payment rule also affects report generation. A system that depends on abstractions instead of concrete implementations allows behavior to change without rewriting every caller. Each principle is really a way of limiting the blast radius of change.
This matters because software is not static. Code is a living argument about how the world works, and the world keeps contradicting it. Requirements shift, libraries evolve, edge cases surface. The most valuable design is therefore not the design that predicts everything, but the design that contains surprise.
Consider a reporting system. If a ReportGenerator class both fetches data from a database, formats it as HTML, sends it by email, and logs audit events, it becomes a choke point for change. If email formatting changes, you are forced to open a file that also knows about queries and logging. This is not just inconvenient. It makes the code semantically noisy, because every concept is trapped in the same object.
Now imagine the same system built from smaller pieces: a data source, a formatter, a delivery mechanism, and an audit logger. Each component has a clearer identity. The architecture itself tells a story: retrieve, transform, deliver, record. That story is easier to extend because it is easier to read.
Here is the crucial insight: SOLID does not merely make code easier to modify. It makes code easier to explain. When responsibilities are well separated, a docstring can describe intent without fighting architectural chaos. In other words, good design compresses the amount of prose needed, while good prose compresses the amount of design ambiguity left over.
Docstrings Are Not Notes. They Are Contracts of Attention
Many developers treat docstrings as afterthoughts, a place to restate the obvious or copy the function signature into English. That wastes their real power. A good docstring is not a summary of syntax. It is a guide to use, expectation, and constraint.
The conventions for docstrings point toward a disciplined form of communication. A one line docstring should be used when something is obvious and truly fits on one line. More substantial docstrings begin with a concise summary, then a blank line, then a fuller explanation. That structure mirrors the way humans process information: first the gist, then the detail.
This format is not bureaucratic fussiness. It is a recognition that comprehension is layered. The summary line tells you what the thing is for. The extended description tells you how it behaves in context, what assumptions it makes, and what exceptions it raises. It is the difference between naming a road and describing the weather conditions on it.
The best docstrings answer questions that the code itself cannot answer cheaply:
- What does this function promise, conceptually?
- What inputs are meaningful versus merely accepted?
- What side effects occur?
- What invariants should callers preserve?
- What subtle edge case would surprise a maintainer?
This is where docstrings become the moral counterpart to SOLID. SOLID asks developers not to create objects that do too much or depend too tightly. Docstrings ask developers not to leave future readers with hidden assumptions. Together they create a codebase with fewer secrets.
There is also a practical distinction worth making: the more explicit the design, the more concise the documentation can be. A function named parse_iso8601_timestamp inside a module dedicated to date parsing needs less explanation than a generic process_data function buried inside an everything class. Better design buys you better naming. Better naming lowers the docstring burden. But when naming and structure still leave ambiguity, documentation closes the gap.
The Real Goal: Reduce the Gap Between Intention and Interpretation
Software fails in a surprisingly human way. Not because the machine misreads instructions, but because humans do. A developer intended one thing, a teammate inferred another, and a future maintainer had to choose between guesswork and archaeology. The true measure of design quality is the size of that gap between intention and interpretation.
This is where the intersection of SOLID and docstrings becomes especially powerful. SOLID reduces ambiguity by making behavior easier to infer from structure. Docstrings reduce ambiguity by making intent explicit in language. Together they attack misunderstanding from both sides.
Imagine a payment service with a method called charge(). If the class violates single responsibility, the method might authorize the card, charge it, retry on failure, emit analytics, and update the customer record. Even with a docstring, the method is still a black box because the design is overloaded. Now split the responsibilities. One component authorizes. One captures funds. One updates state. The docstrings can now be short and precise because the architecture already tells part of the truth.
This creates a useful mental model:
Architecture answers: Where does this responsibility live?
Docstrings answer: What does this responsibility mean?
When those two answers disagree, maintenance becomes expensive. A class may be named like a utility but behave like a workflow engine. A docstring may promise purity while the function silently mutates global state. In those moments, the code is not merely messy. It is dishonest.
Readable software is software whose structure and prose tell the same story.
That is why consistency matters more than verbosity. A concise docstring on a well designed function is often more valuable than a long explanation on a tangled one. Conversely, an elegant architecture without enough explanatory context can still mislead at the boundaries, where domain assumptions and edge cases live. The goal is alignment.
One way to test that alignment is to ask: if you removed the docstring, would the code still communicate its intent? If yes, the design is doing good work. If no, does the docstring clarify a nuance that structure cannot easily express, or is it compensating for a deeper design flaw? That question is often more useful than asking whether documentation is present at all.
A Framework for Writing Code That Ages Well
If you want to write software that remains understandable under pressure, use a two layer approach: shape first, language second.
Layer 1: Shape the code so responsibilities are obvious
Before writing docstrings, make the structure carry as much meaning as possible. Favor small, focused classes. Depend on abstractions where change is expected. Keep interfaces narrow. Separate policies from mechanisms. If one object is becoming a miniature universe, split it.
This is not just style. It is a strategy for preserving cognitive bandwidth. Every unnecessary dependency is one more thing a reader must hold in mind. Every blurred responsibility forces the docstring to defend a design mistake instead of clarifying a good one.
Layer 2: Use docstrings to name the invisible
Once the structure is sane, document what the code cannot express on its own. Explain intent, invariants, edge cases, and domain terms. Use a one line docstring when the purpose is obvious and no further detail is needed. Use a fuller docstring when the contract has nuance.
A helpful rule: document decisions, not machinery. Do not explain that a function adds two integers if the name already says that. Instead, explain why a certain rounding strategy exists, why a default is asymmetric, or why a parameter must be timezone aware.
Layer 3: Let structure and prose verify each other
The real power comes when you treat design and documentation as mutual checks. If the docstring is hard to write, the API may be too broad or unclear. If the class hierarchy is elegant but impossible to explain in one sentence, the abstraction may be premature. If the docstring describes behavior not visible in the interface, the implementation may be surprising in harmful ways.
In that sense, writing docstrings is a design review. It forces you to confront whether the code is truly understandable, not merely functional.
Key Takeaways
-
Design and documentation solve different parts of the same problem. SOLID makes code easier to reason about structurally. Docstrings make it easier to reason about semantically.
-
Use architecture to reduce explanation, not replace it. If a class or function is overloaded, no docstring can fully rescue it. Simplify responsibilities first.
-
Document intent, constraints, and edge cases. Avoid restating the obvious. Explain the reasons behind behavior that is not self evident.
-
Treat docstrings as contracts of attention. They are for future readers who need to know what matters now, not just what the code happens to do.
-
Check for alignment between structure and prose. If the code and docstring tell different stories, that is a sign of deeper design debt.
The Best Code Is Both Well Built and Well Spoken
Software ages the way organizations age: through accumulated assumptions, forgotten rationale, and new people trying to make sense of old decisions. A codebase that lasts is not one that merely runs. It is one that keeps its shape under change and its meaning under scrutiny.
That is why the most mature engineering instinct is not choosing between design and documentation. It is recognizing that good design is a form of silent explanation, and good documentation is a form of explicit design. One makes the system easier to inhabit. The other makes it easier to trust.
If you want code that survives beyond the moment it was written, do not ask only whether it works. Ask whether it can still teach its next reader what it is for, how it behaves, and where its boundaries are. The real mark of quality is not that software can be executed. It is that it can be understood without a detective novel.
When code is both structurally disciplined and verbally clear, it becomes more than implementation. It becomes a durable conversation across time.
Sources
Hatch New Ideas with Glasp AI 🐣
Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)
Start Hatching 🐣