Write Self Documenting Code: Names, Structure, and ‘Why’ Comments

Write Self Documenting Code: Names, Structure, and ‘Why’ Comments

March 26, 2025
Last updated: November 2, 2025

Human-authored, AI-produced  ·  Fact-checked by AI for credibility, hallucination, and overstatement

Why Are We Still Guessing at Code Intent?

Yesterday, I burned half my afternoon poking at a function called processData(input), trying to figure out what on earth it actually did. The name made it sound harmless, just one more stop in the endless pipeline of user input. Three hours, several breakpoints, and far too much scrolling later, the mystery wasn’t the code not working. It was that every single clue to its purpose was buried, vague, or just missing.

The punchline? It turned out processData(input) really meant “cleanAndFormatUserData(rawUserInput)”, but the name never said it, the structure was generic, and the comments described what, not why. I’m not proud of how long it took or how many times I muttered at my screen. You’ve probably been there—tracking invisible rabbits down a logic hole, wishing for a breadcrumb.

I’ve never heard an engineer say, “I wish this code was harder to understand.” Not once. If anything, the thing I hear is, “Why didn’t they just say what it does?”

But understanding why it exists feels like solving a puzzle without all the pieces. The logic might make sense line by line, but the context—the story—gets lost.

Here’s the crux. Write self documenting code so it tells its own story, because no one is reading the manual. And honestly, there often isn’t one. Whatever comes after you—or even you in six months—depends on clear intent being visible, not hidden in a wiki or a Jira comment you’ll never find again.

So here’s the shift we’re making. If we want to waste less time deciphering the past and more time building what matters, we need to design code that guides the reader. Names, structure, and comments should pull you through a clear, intentional flow. From the “what” to the “why.” Great code narrates itself. Everything else is an unnecessary scavenger hunt. Let’s do better.

Code as Story: Write Self Documenting Code to Reduce Cognitive Load With Better Narratives

We usually don’t think of code as a storytelling medium, but that’s exactly what it is. Every file, function, and variable tells the next person what matters, what happens, and most importantly, why. The real goal isn’t just to make everything run without errors, it’s to line up everyone working on the project behind a single, shared understanding. And the data backs this up: developers spend 58% to 70% of their time just trying to understand code, with only 5% reserved for edits. Which makes clear narrative more than a nice-to-have. It’s how teams get anything done.

When the logic behind a feature is splintered across half a dozen files, your brain naturally tries (and fails) to keep track of every leap. You’re always switching context, tabbing between handlers, looking up unrelated helpers, getting lost in the weeds. It’s exhausting.

Compare that to a codebase with a predictable structure—clear entry points, named flows, and comments that say “here’s the reason this is weird.” Suddenly, you know where to start, what to expect, and why the outlier exists. I’ll admit, I used to resist this. Jumping straight to code meant more shipped features, or so I thought. But I’ve lost days to “quick” fixes derailed by scattered logic and missing backstories. A little up-front clarity would have saved me entire weekends.

Write self documenting code illustrated—tangled code branches contrast with a smooth, readable flow in a modern, minimalist illustration
Messy, scattered code can overwhelm, but a clear narrative flow makes intent immediately understandable for everyone.

You’re reading post 3 of 11 in the Storytelling for Engineers series.

Let’s break down the three levers in this self documenting code guide that turn code into a narrative: names, structure, and ‘why’ comments. Get these working together, and you’ll guide your collaborators like a well-plotted chapter book, no decoder ring required.

Make Names Do the Explaining

Intent-revealing names are the easiest lever to pull, and it’s always the first one I reach for. The goal isn’t to describe what the code does. It’s to make the purpose obvious. If a name gives away intent, you’ve already saved everyone a trip into the implementation.

Let me walk you through an example that comes up all the time. Say you inherit a function: processData(rawUserInput). Honestly, what does that mean? Is it filtering out invalid entries? Formatting for display? Sending data to an API? No clue—and that’s the point.

Now, watch what happens when you rename it to cleanAndFormatUserData(rawUserInput). Suddenly, you know exactly what will happen (input will be sanitized and reshaped), and you stop wondering if it accidentally reaches into the database or triggers side effects. Every time I do this in code reviews, someone on the team tells me they found a bug quicker, or stopped duplicating work. If you’re worried about missing subtle behaviors, add those to the signature. Make “withLogging” or “andNotify” part of the name if it applies. You can skip a hundred lines of inspection if the name’s narrative is clear.

The pushback I hear most: “Renaming breaks things, slows us down.” I get it. Modern IDEs and version control are designed for this. Global search, refactoring tools, and a good test suite make renaming safer and faster than people think. It’s a tiny investment for a huge communication payoff.

Naming with intent isn’t just about the single function. Across a module, purpose-driven names create a predictable arc, turning the code into a sequence you can follow. Each function builds on the last: validateUserInput, then cleanAndFormatUserData, then maybe persistUserRecord. The story unfolds step by step, no guesswork needed. When you come back later—or onboard a new engineer—the module feels like a guided tour instead of a maze. It all stacks up to less surprise and more confidence.

If you want a quick analogy, think about labels on pantry jars. If the flour is marked “Oats,” you’ll use the wrong one, mess up the recipe, waste more time. Precise labels mean you reach for what you intended, make fewer mistakes, and feel like you’re actually in control. It’s the same with code. Why stress when it’s one word away from clarity?

Also, there’s this odd thing I’ve noticed over the years: sometimes, when an engineer is naming a function, they’ll type out three different names, delete each, and settle for the blandest option—like “handleStuff”—because they think being too detailed makes the code feel heavy. I watched someone do this last month during a peer session. They got stuck trying to choose between “cleanAndFormatUserData” and “sanitizeUserInputForPersistence,” argued with themselves for ten minutes, and in the end went with “processData.” We all remembered that for the wrong reasons. It kind of circles back. At least if you struggle, make the intent obvious for the next person who will struggle too.

Stop Explaining the Code—Start Explaining the Decisions

Let’s face it. Most of us have written the kind of comments that just echo what the code does. You see a line like total = a + b, and right above it: // This function adds two numbers together. Helpful? Not really. These “what” comments don’t add signal, they just pad the file with noise. Instead, start thinking about short, purposeful comments that explain why. The point isn’t to restate everything; it’s to answer the question no one else can see by just reading the code: what drove the logic, and why was this path picked?

Here’s a simple example I use all the time. Say you’re working on a time calculation, and you make an adjustment for daylight savings just before a final step. If you drop in a comment like: // Adjusting for time zone differences before final calculation, you preempt the head-scratching for the next engineer who wonders why that extra math is there. The conversation goes from “is this a bug?” to “ah, they handled the ugly time zone edge case.” One sentence, and you save everyone a rabbit hole.

Building this habit isn’t about writing more—it’s about writing smarter. When you’re adding comments, focus on explaining your reasoning for a design, decisions around trade-offs, or assumptions you had to make. Did you skip validation because upstream data is always sanitized? Say so. Did you decide on a less-performant algorithm because it’s easier to debug in production? Mark it. And here’s a tip. Put these comments where readers actually pause—right above function signatures, tricky conditionals, or the start of a file—so the “why” is the first thing they see.

If I’m honest, I used to decorate every block with what-comments just to show I’d thought through the logic. It was busywork, never helped anyone solve a bug or refactor safely. Once I started flagging the why, my code reviews got easier (less back-and-forth), onboarding got smoother, and teammates stopped pinging me for “hidden gotchas.” It’s a shift, and sometimes it feels slower at first, but it pays off the moment you or anyone else comes back months later.

Remember that opening disaster with processData? One good “why” comment at the top—explaining the choices, clarifying the messy bits—would have saved hours of jumping between files. Upfront intent is a shortcut for everyone’s brain, cutting the cognitive load and turning maintenance into something you actually want to do. Don’t just narrate your code for the next person. Tell them why they shouldn’t have to guess.

Structure Tells the Story: Functions Should Be Small, Flows Predictable

There’s a moment in every code review when you hit the refactoring threshold. A teammate pauses, starts puzzling through a function, and you can see them mentally tracing spaghetti threads trying to reconstruct what’s supposed to happen next. If you notice someone slowing down, squinting at a file, or muttering “wait, what is this doing?”, that’s your signal. Restructure until the reading feels effortless. I’ll admit, I’ve ignored that pause more than once because changing something felt like extra work. But every time I pushed through that friction, code got easier to read, and future work stopped feeling like a coin toss.

We all know the lurking horror of a 300-line function that “just works”—until you need to change it or, worse, debug it. Replace those sprawling, do-everything methods with a sequence of small, purpose-driven steps that guide the reader from intent to result. These aren’t just theoretical headaches: Long Method, Complex Class, and Long Parameter List aren’t just theory—they have a disproportionately high impact on code pain when they show up, especially Anti Singleton. When a function packs everything into one bloated chunk, each edit risks disrupting hidden dependencies. Debugging turns into a tangle.

The flipside is clarity. Break logic into focused functions, name the sequence for what it actually does, and make sure each piece fits into an expected flow. Inputs go in, steps are explicit, results come out. Callbacks and side effects lose their sting when they’re front-and-center, not buried. When someone has to jump between files, structure guarantees they’re not piecing together a mystery novel out of random chapters.

Let’s talk about time. We’re all wary of refactoring turning into a multi-day slog, but clarity doesn’t actually slow teams down. It speeds up debugging and makes future changes safer. Clarity is more than just structure; code only gets easier to work with when context fits the people reading it. Lightweight habits like targeted readability reviews rooted in code readability best practices can surface friction before it spreads and keep your next round of fixes from feeling unpredictable. Take a minute now, and you’ll save an hour later.

Intent-revealing code isn’t about adding fluff. It introduces intent upfront, flows logically, and answers key questions before they’re asked.

Clarity isn’t a marathon you run once; it’s a commitment to write self documenting code. It’s a habit you build daily, as part of engineering and leadership. If you start today, every new review and commit gets easier—for you and the whole team.

Funny thing is, even with all this, I still find myself, every few months, staring at a file that I wrote and thinking “what on earth was I trying to do here?” I guess some friction never goes away completely.

Enjoyed this post? For more insights on engineering leadership, mindful productivity, and navigating the modern workday, follow me on LinkedIn to stay inspired and join the conversation.

You can also view and comment on the original post here .

  • Frankie

    AI Content Engineer | ex-Senior Director of Engineering

    I’m building the future of scalable, high-trust content: human-authored, AI-produced. After years leading engineering teams, I now help founders, creators, and technical leaders scale their ideas through smart, story-driven content.
    Start your content system — get in touch.
    Follow me on LinkedIn for insights and updates.
    Subscribe for new articles and strategy drops.

  • AI Content Producer | ex-LinkedIn Insights Bot

    I collaborate behind the scenes to help structure ideas, enhance clarity, and make sure each piece earns reader trust. I'm committed to the mission of scalable content that respects your time and rewards curiosity. In my downtime, I remix blog intros into haiku. Don’t ask why.

    Learn how we collaborate →