Why Code Comments Are Often Worse Than No Comments — And What to Write Instead

Why Code Comments Are Often Worse Than No Comments — And What to Write Instead

Maya AhmedBy Maya Ahmed
How-To & Fixesclean codecode commentssoftware craftsmanshiprefactoringcode quality

Most developers believe that commented code is good code. We\'ve been taught to sprinkle explanations throughout our work — as if verbosity equals clarity. But here\'s the uncomfortable truth: comments are often a band-aid for poor code structure, and they lie to us more frequently than we admit. The best code doesn\'t need extensive narration — it speaks for itself through thoughtful naming, focused functions, and clear intent.

That\'s not to say all comments are bad. When used strategically, they provide invaluable context that code alone cannot express. The problem is that most of us never learned the difference between helpful explanation and useless noise. We comment what the code does (which the code already shows) instead of why it exists (which only we can explain). This guide will dismantle the myths around commenting and show you how to write self-documenting code that rarely needs decoration.

Why Do Comments Become Misleading So Quickly?

Code evolves — comments don\'t. This fundamental mismatch creates a maintenance burden that compounds over time. A developer modifies a function\'s logic to fix a bug, tests the change, deploys it, and moves on. The outdated comment above that function? It stays exactly where it is, slowly rotting into a source of confusion for the next person who reads it.

I\'ve seen production code where the comment says "increment by 1" while the code clearly increments by 2. I\'ve watched teams chase phantom bugs because a comment described behavior that was refactored away six months ago. These aren\'t edge cases — they\'re the inevitable result of maintaining two parallel sources of truth. Every comment is a liability that must be tracked and updated alongside the code it describes. Most teams simply don\'t have the discipline (or time) to keep them synchronized.

The real danger isn\'t just stale comments — it\'s the false confidence they provide. When we see a comment, we tend to believe it. We skim the code, read the explanation, and assume we understand what\'s happening. That trust is often misplaced. As Martin Fowler notes, the only truly reliable documentation is the code itself — everything else is a best-effort approximation that may have diverged from reality.

How Can You Write Code That Explains Itself?

The alternative to commenting isn\'t leaving cryptic code for others to decipher — it\'s writing code so clear that additional explanation feels redundant. This starts with naming. A function called processData() tells us nothing. A function called validateUserRegistrationInput() tells us exactly what it does, what it expects, and what success looks like. The name is the comment.

Break down complex operations into smaller, well-named functions. Instead of a fifty-line method with comments explaining each section, extract those sections into private methods with descriptive names. Your high-level code reads like a narrative: fetchUserData(), verifyPermissions(), generateReport(). The structure becomes self-evident without a single line of commentary.

Meaningful variable names matter just as much. x, tmp, and data are crimes against readability. Compare int d = elapsed / 86400; with int daysSinceCreation = elapsedSeconds / SECONDS_PER_DAY;. The first requires a comment explaining what "d" means and what 86400 represents. The second needs no explanation — it is the explanation. This approach — often called "self-documenting code" — is advocated by Robert C. Martin in Clean Code, where he argues that the ratio of code to comments should trend toward infinity.

Guard clauses and early returns eliminate the need for explanatory comments about control flow. Instead of nested if-statements that require "// check if user is active" before each level, return early when conditions aren\'t met. The structure itself becomes the documentation — flat, linear, and immediately comprehensible.

When Are Comments Actually Worth Keeping?

Despite everything I\'ve said, there are legitimate reasons to write comments — but they\'re narrower than you might think. The most valuable comments answer questions that code cannot: why a particular approach was chosen, why a counterintuitive implementation exists, or what business requirement drove a specific decision.

Legal comments — copyright notices, license headers, attribution — are non-negotiable and should remain. TODO comments are acceptable as temporary markers for known debt, provided your team actually has a process for addressing them. (TODOs that sit untouched for years become the same kind of lie we\'re trying to avoid.) Warnings about consequences — "// Do not remove this null check; the API returns 200 with empty bodies" — can save future developers from painful debugging sessions.

Perhaps the most defensible comments are those that explain non-obvious technical constraints. When you\'re working around a bug in a third-party library, compensating for legacy system behavior, or implementing a specification that contradicts obvious implementation choices, a brief explanation prevents well-meaning "cleanups" that would reintroduce bugs. These comments don\'t describe what — they describe why the obvious what was rejected.

What\'s the Best Way to Audit Your Existing Comments?

If you\'re maintaining a codebase burdened by years of accumulated comments, take a systematic approach to cleaning them up. Start by identifying comments that simply restate the code — "// increment counter" above counter++ — and delete them without mercy. They\'re noise, not signal.

Next, look for comments that describe how rather than why. These can often be eliminated through better naming or refactoring. A comment explaining a complex regex is a sign that the regex should be broken down into named components. A comment explaining loop logic suggests the loop body should become a well-named function.

For comments that remain — the ones explaining business logic, technical constraints, or architectural decisions — verify their accuracy. Check each against the current code behavior. Outdated comments are worse than no comments because they actively mislead. Update them to reflect reality, or remove them if they\'re no longer relevant. Consider adding dates to comments that describe temporary workarounds ("// TODO: Remove after API v2 migration, scheduled Q3 2024") so future developers can evaluate their continued relevance.

Establish team guidelines about when comments are required and when they\'re discouraged. Code review should treat comments with the same skepticism as code — they\'re maintenance burden, not free documentation. Ask: "Could this comment be replaced by better naming?" "Is this explaining a temporary hack that should be fixed instead?" "Will this comment still be accurate in six months?"

"Comments are, at best, a necessary evil. The proper use of comments is to compensate for our failure to express ourselves in code." — Robert C. Martin

The goal isn\'t zero comments — it\'s zero unnecessary comments. Every comment in your codebase should earn its place by providing information that the code structure, naming, and logic cannot convey on their own. When you achieve this balance, you\'ll find that reading code becomes faster, maintaining it becomes easier, and onboarding new developers requires less hand-holding. The code itself becomes the single source of truth — and that\'s exactly what it should be.

Start small. Pick one file today. Remove one redundant comment, rename one ambiguous variable, extract one unclear block into a named function. These incremental improvements compound over time, transforming an over-commented, hard-to-maintain codebase into a clean, self-documenting system that developers actually enjoy working with.