Comment the Why *and* the What
People say "comment the why, not the what", the idea being that the code should be self-documenting and the comments should only be a last resort for explaining stuff. Comments can get out of sync, but the code is always the code, so there's no need for unnecessary redundancy.
I disagree: No matter how self-documenting the code is, comments help a lot with understanding stuff. Whenever I go back to an old project, I find the comments far more useful to reorient myself than my code or my tests. Here are some cases where adding the "what" is really helpful.
Providing Context
One of my "self-documenting code" reference points is the FitNesse repo. Robert Martin wrote it, so it's gotta be a quintessential example of Clean Code, right? Anyway, check out the Shutdown class. It shuts down a running instance. Where does the actual API call happen, though?
It happens here, in a completely different file. You have to navigate through four methods and three (!) files to find it. That sucks! One comment would have made this a lot more sensible:
// http call happens in RequestBuilder:
// '/?responder=shutdown'
This isn't because Martin architected the program badly. This is a problem with all big codebases. When you're reading code, you're only seeing the local structure of that code, not the global structure of the entire codebase. Good for understanding what this specific function is executing, bad for understanding it in the context of the program. Provide that context to your readers.
And don't even get me started on interprogram structure! If your code calls another macroservice, include a brief description of the other service. If your loggers persist to Splunk, include a link on where to find the production logs.1
Describing Essential Algorithms
It takes 20 lines of code to implement your functionality for the happy path. Then you need to handle the sad paths. Then you have to cover the weird edge cases. Then you have to add logging and data munging and debugging probes and configuration. At some point, most of the code you're writing goes into "overhead": the support code that makes the functionality survive in the real world.
While necessary, all these layers of validation and auditing and modularization obscure the essence of the functionality. Reading the code no longer tells you what it "does". Without knowing the essence, the reader can't determine why all of the extra stuff is there, which means they can't distinguish signal from noise, which means they can't find the essence. The reader can work this out by spending a lot of time reading the code and reverse-engineering the core functionality. Or you could stick the "idealized" function in a comment and save them the effort.
(These kinds of comments are at higher risk of going out of date; while the essence changes less often than the overhead, it does change faster than global structure. It probably makes sense to mention specific functions and modules in the comments, which would make divergences more obvious.)
Explaining Optimized Code
Kind of a subclass of essential algorithms. Performant code is going to be less clear than non-performant code. This is because performant code needs to be legible and performant, while non-performant code just needs to be legible.
Yes, I know "premature optimization is the root of all evil". But at some point you'll optimize something. You will rewrite some part of your code to make it faster or use fewer resources. And that will come at the cost of legibility. So write "what" comments so people know what your optimized code actually does.
Skill differentials
There's always going to be skill differentials on any project, and they can come from a LOT of places:
- Your coworkers are straight-up more junior than you.
- You have to use a new complicated library, and you're the only person so far who's read the documentation.
- Your library includes a config file that end-users are supposed to customize themselves.
- The problem is best solved using coding or domain techniques you're familiar with but other people are not. Or by a novel abstraction / code design that you came up with for this problem, or by a different language entirely.
There are always going to be things you know better than your teammates, and things they know better than you. Code that's self-documenting to the expert may not be self-documenting to everyone else. Make everyone's lives easier and annotate your code with lots of explanations, so they can all get up to speed quicker.
(These comments can often be temporary. When everybody is up to speed, consider removing them.)
A special case of this is…
Language Weirdness
I know everybody makes fun of this:
i++; // increment i by 1
But that's only an unnecessary comment because you expect i++
to be obvious to any reader. Now how about this comment?
b=( "${a[@]}" ) # copy array
This is the shortest correct way to copy an array in bash. It's really no different than the i++
. Someone who's an expert in bash can instantly recognize that as "copy array".
But most people who use Bash aren't experts, they've got a glue script they might look at once a month. Either they have to look up what that weird sequence of sigils is every time, or they can read the comment.
Another example: I heavily configure my vim and write custom functions. I have lots of comments like this:
" a =~ b checks if b regex-matches a
The only place I ever see =~
is in Vimscript, and I don't care about Vimscript enough to memorize weird operators, so I just stick a comment there and don't think about it.
ASCII Art
Some information just needs to be presented in 2D. John Regehr has a good list of examples here.
Miscellaneous Communications
While I'm putting this last, I think it's the most important category. It just covers a lot of more specific things, so it's also a miscellaneous dumping bin.
The program is a dialogue between us and the machine. We use descriptive variable and function names to make the machine more comprehensible to outside observers. Programmers, reading your self-documenting code, are interpreting the meaning based on your names and structures.
Comments, in contrast, communicate between developers. They're a chance to speak one human to another. What do you want to directly tell other programmers?
Think of it like, I don't know, printing out an official memo and then writing notes in the margins before passing it off to somebody. There's all sorts of information that can't (or shouldn't) be part of the code but still should be communicated to other developers. Just a few examples!
- Negative information: "We do NOT do X here because Y."
- Speculation: "If we need to scale up, this will probably be the bottleneck."
- Tips: "This function is currently idempotent, but that's an implementation detail we intentionally don't rely on."
- Warnings: "This routine is really delicate and fails in subtle ways, make sure you talk with XYZ if you want to change it."
Since these comments are communication, treat them as such. Timestamp and sign the comments so people know who is commenting and when they did it. That way they know who to talk to get more information, how likely communications are to be obsolete, and what you need to expand on when you give your two-weeks notice.
Common Objections
Put it in the issue tracker
These shouldn't be comments! They should be in commit messages, or the wiki, or the issue tracker, etc
You have to know the information is there to go looking for it. If the communication is in the commit logs, you might not know it even exists, so why would you try to find it? Whereas comments are right next to the code. If you're reading the code, you know about the comments.
Comments get out of date
Comments can lead you astray, because they get out of date. The code is the only source of truth.
While better programming process fixes a lot of this, I don't like answers that boil down to "try harder". But even incorrect comments can be better than no comments, as long as they're a minority! If you have no comments, you have to build your mental model of the code from scratch, which is tedious and error-prone. With comments, you can check that the code conforms with the comments, which is much faster. Kinda like how it's easier to verify a solution to an NP-complete problem than it is to solve it.
Comments bloat files
Ugh yeah I agree, it's really annoying. The more comments you have, the less code you can have on the screen at once, and also the comments are visually distracting. I think better tooling can deal with this, but "better tooling!" isn't much better than "try harder!" as an answer.
We don't have a well-developed "discipline of comments" in software. Most of the material out there is about why you shouldn't write comments or (less often) why you should. There's very little on what comments should look like or commenting techniques or commenting case studies. Then people don't know how to write good comments, so they say "comments are useless", so there's nobody developing a discipline of comments.
Comments are good, write more comments
Book Update
(I explain the book here if you're new.)
I'd say I'm 10-20% of the way towards a presentable early access. So yeah, probably not going to have something shareable by the end of this month, which I'm sure absolutely everybody saw coming. I apparently forgot to factor "having a social life again" into my schedule.
One positive thing: I saw that people really liked the last newsletter about informing software design with logic. That fills me with confidence that this book is needed.
(Also thinking of renaming it to Logic for Programmers. A little less threatening, but it's also less informative.)
-
"But the link is in the config file!" Most production configs aren't stored in the code repo, they're injected by the build system. Which is a different system. Include a link to that, too! ↩
If you're reading this on the web, you can subscribe here. Updates are once a week. My main website is here.
My new book, Logic for Programmers, is now in early access! Get it here.