Some developers never comment their code, while others comment too much. The former believes that the code should be self-documenting, while the latter has read somewhere that they should always comment their code.
Both are wrong.
I don’t believe in self-documenting code. Yes, we should rewrite unclear code to make it more obvious and use meaningful, correct names, but some things can’t be expressed by the code alone.
Commenting too much doesn’t help either: comments start to repeat the code, and instead of aiding understanding, they clutter the code and distract the reader.
There’s a popular technique for avoiding comments: when we want to explain a block of code in a comment, we should move this piece of code into its own function and use the comment text as the function name.
Here’s a typical example of code I usually write:
It’s a long function, but I don’t see any benefit in splitting it. There are only two levels of nesting, and the overall structure is mostly linear. Comments provide a high-level overview and the necessary context, allowing us to skip blocks we’re not interested in.
Overall, I don’t think that splitting a function into many small functions just because it’s “long” makes the code more readable. Often, it has the opposite effect because it hides important details inside other functions, making it much harder to modify the code.
Info We talk about code splitting in more detail in the Divide and conquer, or merge and relax chapter.
Another common use for comments is complex conditions:
Here, we have a complex condition with multiple clauses. The problem with this code is that it’s hard to see the high-level structure of the condition. Is it something && something else? Or is it something || something else? It’s hard to see what code belongs to the condition itself and what belongs to individual clauses.
We can extract each clause into a separate variable or function and use comments as their names:
Here, we separated levels of abstraction, so the implementation details of each clause don’t distract us from the high-level condition. Now, the structure of the condition is clear.
However, I wouldn’t go further and extract each clause into its own function unless they are reused.
Info We talk more about conditions in the Avoid conditions chapter.
Good comments explain why code is written in a certain, sometimes mysterious, way:
If the code is fixing a bug or is a workaround for a bug in a third-party library, a ticket number or a link to the issue will be useful.
If there’s an obvious, simpler alternative solution, a comment should explain why this solution doesn’t work in this case.
If different platforms behave differently and the code accounts for this, it should be mentioned in a comment.
If the code has known limitations, mentioning them (possibly using todo comments, see below) will help developers working with this code.
Such comments save us from accidental “refactoring” that makes the code easier but removes some necessary functionality or breaks it for some users.
High-level comments explaining how the code works are useful too. If the code implements an algorithm, explained somewhere else, a link to that place would be useful. However, if a piece of code is too difficult to explain and requires a long, convoluted comment, we should probably rewrite it instead.
I like todo comments, and I add plenty of them when I write code. Todo comments can:
Help us focus on essentials when we write code by writing down everything that we want to do or try later.
Write down known limitations and possible or already planned improvements.
I remove most todo comments before I submit my code for review — they are part of my coding process.
Info You may encounter various styles of todo comments: TODO, FIXME, UNDONE, @todo, @fixme, and so on, though I prefer TODO.
The remaining todo comments can be roughly split into two categories:
The first kind are planned improvements. When we know that we need to do something, it’s best to add a ticket number in a todo comment:
There might be another condition, like a dependency upgrade, required to complete the task:
This is one very good comment!
The second kind of todo comments are dreams: a desire that the code was doing more than it does. For example, error handling, special cases, support for more platforms or browsers, minor features, and so on; but it wasn’t implemented, probably due to a lack of time. Such todos don’t have any deadlines or even the expectation that they will ever be resolved:
Tip Maybe we should start using DREAM comments for such cases…
However, there’s a type of todo comments I don’t recommend — comments with an expiration date:
We can check these todo comments with unicorn/expiring-todo-comments linter rule, so our build will fail after the date mentioned in the comment. This is unhelpful because it usually happens when we work on an unrelated part of the code, forcing us to deal with the comment right away, most likely by adding another month to the date.
There are other conditions in the unicorn/expiring-todo-comments rule that might be more useful, such as the dependency version:
This is a better use case because it will fail only when someone updates React, and fixing such todos should probably be part of the upgrade.
Tip I made a Visual Studio Code extension to highlight todo and hack comments: Todo Tomorrow.
I like to add examples of input and output in function comments:
Such comments help to immediately see what the function does without reading the code.
Here’s another example:
Here, we don’t just give an example of the input and output, but also explain the difference with the original rehype-slug package and why a custom implementation exists in the codebase.
Usage examples are another thing to include in function comments:
Such comments help to understand how to use a function or a component, highlight the necessary context, and the correct way to pass parameters.
Tip When we use the JSDoc @example tag, Visual Studio Code shows a syntax-highlighted example when we hover on the function name anywhere in the code.
We’ve talked about useful comments. However, there are many other kinds of comments that we should avoid.
Probably the worst kind of comments are those explaining how code works. They either repeat the code in more verbose language or explain language features:
Or:
Such comments are good for coding tutorials, but not for production code. Code comments aren’t the best place to teach teammates how to use certain language features. Code reviews, pair programming sessions, and team documentation are more suitable and efficient.
Next, there are fake comments: they pretend to explain some decision, but they don’t explain anything, and they often blame someone else for poor code and tech debt:
Why do Chinese and Koreans need a different time format? Who knows; the comment only tells us what’s already clear from the code but doesn’t explain why.
I see lots of these comments in one-off design “adjustments.” For example, a comment might say that there was a design requirement to use a non-standard color, but it won’t explain why it was required and why none of the standard colors worked in that case:
And by lots, I mean really plenty:
Requirement is a very tricky and dangerous word. Often, what’s treated as a requirement is just a lack of education and collaboration between developers, designers, and project managers. If we don’t know why something is required, we should always ask. The answer can be surprising!
There may be no requirement at all, and we can use a standard color from the project theme:
Or there may be a real reason to use a non-standard color, that we can put into a comment:
In any case, it’s our responsibility to ask why as many times as necessary; otherwise we’ll end up with mountains of tech debt that don’t solve any real problems.
Comments enrich code with information that cannot be expressed by the code alone. They help us understand why the code is written in a certain way, especially when it’s not obvious. They help us avoid disastrous “refactoring”, when we simplify the code by removing essential parts.
However, if it’s too hard to explain a certain piece of code in a comment, perhaps we should rewrite such code instead of trying to explain it.
Start thinking about:
Removing comments that don’t add anything to what’s already in the code.
Adding hack comments to document hacks in the code.
Adding todo comments to document planned improvements and dreams.
Adding examples of input/output, or usage to function comments.
Asking why a commented requirement or decision exists in the first place.