The Comment That Should Have Been Code
You’re about to write a comment above a block of code: “// calculate the adjusted total after discounts and tax.” It feels responsible — you’re being helpful, leaving a note for the next person. But pause on why the note is needed. It’s needed because the block below it doesn’t say what it does clearly enough on its own, and the comment is a patch over that gap. Very often the better move isn’t to write the comment; it’s to pull that block into a function called adjustedTotalAfterDiscountsAndTax and delete the comment, because now the code says the thing the comment was going to say, in the one place that can’t drift out of date.
That’s the core problem with explanatory comments: they’re a second copy of the truth, and like every second copy, they drift. The code changes; the comment doesn’t; and now the comment is worse than nothing, because it actively lies. A reader trusts it, or wastes time reconciling it with code that no longer matches, or learns over time that the comments here can’t be trusted and stops reading them entirely — at which point the good comments die alongside the bad ones. A comment that restates what the code does is a maintenance liability that provides its value exactly once, when it’s fresh, and decays from there.
So the first question when you reach for a comment is whether the code could just say it instead. A well-named function replaces a “what” comment and can’t fall out of sync, because it is the code. A well-named intermediate variable turns a dense expression into something that reads in plain language — isEligibleForRefund instead of a tangle of conditions the reader has to decode and a comment has to summarize. Breaking a long function into named steps often eliminates a whole cluster of comments that were really section headers for a function that was doing too much. In each case the comment was a signal that the code wasn’t expressing itself, and the fix was to make the code expressive, not to annotate its silence.
But this isn’t an argument against all comments, and the “never comment” crowd overshoots into a different failure. There’s a whole category the code genuinely cannot express, and it’s the category worth writing: the why. Code says what it does; it cannot say why it does it, what it deliberately isn’t doing, what constraint from the outside world forced this shape, what alternative was tried and abandoned and shouldn’t be tried again. “We clamp this to 100 because the vendor’s API rejects larger batches, learned the hard way” is a comment no renaming can replace, because the reason lives nowhere in the code and would otherwise be lost. Those comments don’t decay the way “what” comments do, because the code changing doesn’t make the reason wrong.
So the discipline sorts the impulse into two paths. When you want to explain what the code does, treat that as a prompt to make the code clearer — better names, smaller functions, an extracted variable — until the explanation is unnecessary. When you want to explain why it does it, write that down, because it’s information that exists nowhere else and that the next person will genuinely need. The goal isn’t more comments or fewer; it’s code that says what it does through its own structure, annotated only where it can’t speak for itself. A comment should be the thing you write when you’ve made the code as clear as it can be and there’s still something true left to say.