Monthly Archives: May 2021

‘Self-Documenting Code’ is Not Enough: Why Code Needs Inline Comments

But don’t waste your time documenting what you did; document what you thought.


Coding is hard. And the thing you spend the most time doing isn’t typing: you spend the most time reading and trying to understand code. This applies not only to other people’s code, but also to your own, more than 10 minutes after you wrote it.

This is why documenting your code is so critical, as well as taking the time to write clean, readable code. Clear code is far better than clever code. (In fact, I might go so far as to say that clever code is usually a premature and self-justifying optimization, unless accompanied by myriad apologies and disclaimers.)

But, somewhere along the way, the virtue of clarity somehow becomes controversial. Some of us are sailors on the Aegean, and hear the siren song of ‘self-documenting code’ calling us to dash ourselves against the rocks of piety and self-delusion.

Today, I spoke with a wonderful mid-level engineer about some code that lacked any inline comments whatsoever. (He hadn’t written it.) He kind of agreed that was an issue… but then also disagreed, and explained why. A little while ago, a more senior engineer (herself no dummy) had told him comments were bad, and distracting… that all code should be self-documenting. Which… sounded right. Who could disagree with the idea of naming things well? Of making code readable? So, he stopped writing comments.

First, let’s just acknowledge: there are a LOT of useless comments out there. If the CSS selector is ::-webkit-scrollbar, and then it has a rule that says background: transparent, I do NOT need an annotation next to that rule which that combines 2 of those obvious keywords with an action verb to tell me it will: /* make scrollbar transparent */. I mean… I’m grateful someone’s commenting, and I’m not saying they’re a bad person who should burn in hell, but just… they should save their effort for something actually useful, eh?

Further, there’s a particular affliction of certain IDE’s and other automated tools, whereby they produce metric tons of boilerplate comments that nobody ever reads, and which in fact reduce the overall signal-to-noise ratio. This is also bad.

But don’t throw the baby out with the bath water. The problem is not the institution of inline documentation of code, but its misuse. Just as statistics themselves are not dishonest (a la: “lies, damn lies, and statistics!”), inline comments are not a bad thing… but they are sometimes abused to bad effect.

Further, writing code that is as self-documenting ** as possible ** is an unmitigatedly Good Thing. Variable, class and function names should indeed be rich and explanatory! Comments that re-explain what’s objectively obvious are noise without benefit. There are only two hard things in CS, etc., etc.

But not all code can self-document, and not all things can be present in code. In particular, code is only your implementation of an idea, but the most important things to document are those things which are not present:

  • The intent/purpose of code
  • Whatever’s hiding on the other side of an interface (call and return signatures, let alone behavior)
  • Any alternative implementation paths which didn’t work for some reason: these are landmines to warn your successors away from

At a minimum, inline documentation should address those things: it should explain that which your code cannot, at least not without successors needing to dive deep into reading the source of all the things on the other side of an interface. And after all, isn’t the point of documentation to save time? To avoid reinventing wheels, to avoid breaking useful abstractions?

But there’s also another point to documentation, and it’s the same point often raised about testing: writing about your code forces you to think it through, to understand it yourself better than you would if you didn’t examine and define your assumptions. And that kind of documentation interlocks with your testing: when you write down the arguments you expect your code to accept, and the responses it should provide, you give yourself a list of targets to hit, and you remind yourself of all the ways you could blow it.

Sometimes your user story defines those targets for you… although unless you provide a link to the story, that doesn’t help anyone who comes after you. And other times, as when you write a helper utility, or some other piece of behind-the-scenes plumbing, not even JIRA can tell anyone what you were trying to do. It’s up to you to explain that, and up to you to force yourself to be clear and explicit about your intentions… so that the resulting quality of your implementation can document itself.

Moreover, in stating your intentions and assumptions… you allow your successors to determine where your code failed to live up to them. But if you state your assumption (for instance that you expect only numbers as arguments to a function), you let me help find what you overlooked (that Javascript and other dynamically-typed languages can easily coerce non-numeric values in ways you didn’t anticipate).

Last, half the point of code review is for the reviewer to test whether code is clear and understandable without requiring an exorbitant amount of context, and without requiring the reviewer to go read a bunch of source somewhere else:

Reduce WTF/m. Comment your code, not by restating what’s obvious, but by exposing what’s not.