Table of Contents >> Show >> Hide
- Why “Good Code Documents Itself” Sounds True (Until It Doesn’t)
- The Hall of Fame: Other Jokes Developers Tell Themselves
- What “Good Documentation” Actually Means in 2026
- Comments That Pay Rent: The Only Ones Worth Keeping
- API Documentation: Docstrings, Javadoc, and XML Comments
- The Documentation Stack That Actually Works
- How Documentation Rot Happens (And How to Prevent It)
- Practical Examples: Turning “Self-Documenting” Into Actually Documented
- Conclusion: The Funniest Joke Is Technical Debt Interest
- Bonus: of Real-World Experience With This Myth
There are two types of developers in the world: the ones who say “good code documents itself,” and the ones who have
been paged at 2:00 a.m. because someone deleted the “obviously safe” line that was “basically self-explanatory.”
If you’ve ever stared at a function named process() that calls doThing() which returns result,
congratulationsyou’ve met the comedy genre known as Self-Documenting Code.
This article is a friendly intervention for the stories we tell ourselves to avoid writing documentation:
“We’ll do it later.” “The tests are the docs.” “It’s obvious.” “Future me will remember.”
(Future you would like to file a formal complaint.)
Why “Good Code Documents Itself” Sounds True (Until It Doesn’t)
The idea isn’t totally wrong. Clear naming, small functions, and sensible structure absolutely improve
code readability and software maintainability. If your variable names look like a captcha,
no amount of documentation will save you. But here’s the catch: even pristine code mostly tells you what it does
and how it does itnot why it exists, what trade-offs were made, or what dragons live just off-screen.
“Self-documenting” code is like a well-labeled spice jar. Helpful! But it still doesn’t tell you:
Why is the salt in a locked cabinet? Why does the paprika smell like smoke? Why is there a note that says
“DO NOT OPEN DURING DEPLOY”?
Code can communicate intent. Context is a different language.
Intent is “this function validates an email.” Context is “we accept internationalized domains, we normalize
Unicode, we reject plus-addressing for compliance reasons, and this decision was made after the vendor incident of 2022.”
That second sentence is not something your if-statements can reliably whisper to the next engineer.
The Hall of Fame: Other Jokes Developers Tell Themselves
- “We’ll document it after we ship.” Translation: “We will never speak of this again.”
- “The tests are the documentation.” Translation: “Decode this mystery novel written in assertions.”
- “It’s obvious.” Translation: “It was obvious to me during a caffeine event.”
- “No one reads docs.” Translation: “No one reads bad docs.”
- “Temporary solution.” Translation: “Permanent feature with commitment issues.”
- “This TODO is important.” Translation: “This TODO will outlive the team.”
Notice what these have in common: they treat documentation as decoration instead of infrastructure.
But documentation isn’t a school essay. It’s a tool for reducing uncertaintylike a map, not a memoir.
What “Good Documentation” Actually Means in 2026
The goal isn’t to write a novel. The goal is to help a reader answer the questions they will inevitably have:
- What does this do? (Quick summary.)
- How do I use it safely? (Inputs, outputs, edge cases.)
- Why does it exist? (Business rules, constraints, trade-offs.)
- What should I not do? (Foot-guns, gotchas, performance traps.)
- Where do I learn more? (Pointers to deeper docs, runbooks, ADRs.)
If your documentation consistently answers those, your codebase becomes easier to onboard to, easier to modify,
and harder to accidentally destroy. That’s not bureaucracy. That’s survival.
Comments That Pay Rent: The Only Ones Worth Keeping
The best rule of thumb is simple: code should explain what and how; comments should explain why.
“Why” includes intent, constraints, trade-offs, and non-obvious decisions that a future reader can’t reconstruct
from syntax alone.
Bad comments: narrating the obvious
Good comments: capturing constraints and reasoning
If you can delete the comment and lose nothing, delete the comment. If you can’t delete the comment without losing
critical context, that comment just earned its health insurance.
Three comment types that age well
- Rationale: “We do X because Y.”
- Boundaries: “This must remain true or everything breaks.”
- Warnings: “This looks weird on purpose. Don’t ‘fix’ it.”
Also: keep comments in plain, understandable English. If your comment requires a second comment to explain it,
you’ve reinvented confusion as a service.
API Documentation: Docstrings, Javadoc, and XML Comments
Inline comments are for readers inside the implementation. API documentation is for callers who only see the
public surface area. Mixing these two is how we get 400-line method comments that still don’t say what the method returns.
Docstrings (especially in Python)
Docstrings shine when they start with a one-line summary and then expand into the details that matter:
parameters, return values, exceptions, and usage notes. A docstring is a promise to the caller, not a diary entry
about how you felt while writing the function.
Javadoc and similar “doc comment” systems
Doc comments exist because tools can turn them into browsable reference docs. That makes them perfect for
reusable libraries, internal SDKs, and anything used by more than one team. When in doubt, document:
what it does, how to use it, edge cases, and contract constraints.
XML comments in .NET ecosystems
XML documentation comments aren’t there to make your code prettier; they’re there so tooling can surface help in the IDE
and generate reference docs. If you’ve ever hovered over a method and felt genuine gratitude, you’ve experienced
documentation doing its job quietly and effectively.
The Documentation Stack That Actually Works
The most resilient teams don’t bet everything on one form of documentation. They build a small “stack,” where each layer
answers a different question. Here’s a practical setup that works for most products:
1) Self-explanatory code (yes, still important)
- Meaningful names:
calculateTax()beatsdoCalc(). - Small, focused functions.
- Consistent style and formatting.
2) Comments for “why,” not “what”
- Constraints and trade-offs.
- Security, performance, compliance, and compatibility notes.
- “This is weird on purpose” warnings.
3) API surface documentation
- Docstrings/Javadoc/XML comments for public functions and types.
- Examples that show correct usage (especially for tricky APIs).
4) README for “how do I run this?”
A README is your project’s front door. At minimum, it should answer:
what this project is, how to run it locally, how to test it, how to configure it, and where to find deeper docs.
5) Architecture notes (keep them small and specific)
You don’t need a 60-page architecture tome. You need bite-sized “decision snapshots.”
Many teams use Architecture Decision Records (ADRs): short notes that capture a decision, alternatives considered,
and the reason it was chosen.
6) Runbooks for “what do I do when it’s on fire?”
If you have on-call, you want runbooks. They reduce panic and preserve institutional knowledge. A good runbook includes:
how to detect the issue, how to mitigate, how to escalate, and what “done” looks like.
How Documentation Rot Happens (And How to Prevent It)
Documentation rots for the same reason code rots: it gets separated from the workflow. If docs live in a distant wiki
that nobody touches during development, they will become historical fiction.
Make docs part of “done”
- New feature? Update the README or add a short usage note.
- New behavior? Update the API docs or docstrings.
- New decision? Add a short ADR.
- New operational risk? Update the runbook.
Use code review as a documentation detector
If a reviewer asks, “Why is it done this way?” that’s a neon sign that the code needs a comment or a short doc note.
If a reviewer repeatedly asks how to run something, your README is silently requesting assistance.
Keep docs close to the code
The closer documentation lives to what it describes, the more likely it stays updated. READMEs inside repos,
docstrings next to functions, and ADRs inside version control tend to age better than “that one page on the wiki.”
Practical Examples: Turning “Self-Documenting” Into Actually Documented
Example 1: The function name is clear, but the policy isn’t
The code says the rule. It doesn’t say why the rule exists, who asked for it, or what exceptions apply.
The fix is not a paragraphjust the missing context:
Example 2: The “weird” optimization
Without that comment, a well-meaning engineer might “clean up” the code and reintroduce the exact performance regression
you already paid for once.
Example 3: The README that prevents 20 Slack messages
If the only way to run the project is “ask Dana,” the README is currently Dana. Don’t do that to Dana.
Conclusion: The Funniest Joke Is Technical Debt Interest
“Good code documents itself” is a half-truth that becomes a full-time problem at scale. Great code is readable.
Great engineering is readable and explainable. The win isn’t “more docs.” The win is the right documentation
in the right place: small, specific, and written for the next human.
So yes: write clean code. Then do the adult thing and leave a trail of breadcrumbs for whoever inherits it
including you, three months from now, staring into the abyss and whispering, “Who wrote this?”
Bonus: of Real-World Experience With This Myth
I once joined a team that proudly announced, “We don’t write documentation because our code is self-documenting.”
This was delivered with the confidence of someone who had never tried to deploy their own service from a cold start.
The repository had no README, no runbook, and a single wiki page titled “Deployment” that included the timeless instruction:
“Just do the usual steps.” The usual steps, it turned out, lived in the muscle memory of one engineer who was on vacation.
The first incident was a classic: a sudden spike in failures after a “small refactor.” The change looked safevariable
renames, a tidy helper function, nothing dramatic. But the code contained a bizarre conditional branch that seemed redundant.
The refactor removed it. Why was it there? Because one upstream partner occasionally sent malformed payloads that violated the spec,
and that branch was the “be liberal in what you accept” band-aid that kept revenue flowing. The code was readable. The reason
for the weirdness was invisible. The postmortem concluded, as postmortems often do, that we needed “better communication.”
Translation: one well-placed comment and a short note in a runbook would have prevented a messy outage.
Another time, I watched a new hire burn an entire afternoon trying to run tests. The test suite wasn’t broken; it was picky.
It expected environment variables that were set automatically in CI but nowhere else. There were also three different database
configurations: local, dockerized, and “staging-but-not-really.” The code itself was clean, but the developer experience was
a scavenger hunt. We fixed it with a two-minute README update and a tiny script that validated required environment variables.
Productivity went up, frustration went down, and nobody had to learn secret handshakes.
The pattern repeated across projects: “self-documenting” code worked fine when the domain was simple and the team was tiny.
As soon as you add regulations, performance constraints, edge cases from external integrations, and rotating team members,
the myth collapses. The happiest teams I’ve seen treat documentation like brushing teeth: small, consistent, and non-negotiable.
They don’t write novels. They write just enough “why” to make the next change safe, just enough “how” to make the next onboarding fast,
and just enough “what” to make the next incident boring. And honestly? Boring incidents are the funniest joke of all.