1. Introduction
In this tutorial, we’re going to talk about comments. We’ll introduce two categories. Next, we’ll look at some proper and improper examples. For the first, we’ll also look at alternatives.
2. Different Types
A first reason to use comments is to document an API. In Java, it’s called Javadoc, in PHP Docblock and C# calls it “XML Documentation Comments”.
They describe how the API’s should be used. We must realize that users of our API might not have access to our source code. Therefore, the documentation describes the expected input and output. Also, it should explain exceptions and errors which the user should anticipate. The documentation should be clear, complete, and unambiguous.
Other comments focus on the developers of our project. They explain for example a development decision. Or document a problem that has to be fixed in a future version. The rest of this article will focus on those internal developer-facing comments.
3. Goals
In the previous section, we already emphasized that the comments target developers. For a regular commercial project, this could include a broad range of individuals during its lifetime. Namely, juniors developers to very experienced ones, and also people from different cultural backgrounds. We should always keep that in mind while writing the comments.
Ideally, our code should be self-explanatory and we wouldn’t even need any comments. However, sometimes it’s not possible to express something purely using code. See the following fragment containing comment and a regular expression:
// Matches a Dutch zipcode, eg, '1974 XA' or '4844RA'
Pattern DUTCH_ZIPCODE = Pattern.compile("[1-9][0-9]{3} ?[A-Z]{2}");
Without the comment, the regular expression would still be clear for developers experienced in regular expressions or people from The Netherlands. However, for a French junior developer, this regular expression will make more sense with this comment.
4. Drawbacks
Comments are, mostly, correct when written. However, code is dynamic and it’s likely that the code will change, either sooner or later. We might change the code because of changed business logic. However, tests don’t validate our comments. Did we also update them? Did we move the comment together with the code? Or did the comment become an orphan?
So the biggest drawback is that comments don’t always tend to follow, changes in, the code. Sometimes people might even put new code between code and its comment. Sometimes we change code and forget to update the comment. Because of that, the comments could start misleading us.
Another issue is that a lot of projects have guidelines that require comments. However, having non-informative comments will only train our brains to filter them away. Thus increasing the risk that we fail to update those comments when needed.
Because of these drawbacks, we should always prefer, self-explanatory code over comments. Comments can then be an exception. And, when they’re we might remember to keep them up-to-date.
5. Improper Usage
Various people have different opinions about which comments are useful and which aren’t. We did our best to group some comments commonly agreed to be less useful. And we also explain some alternatives.
5.1. Stating the Obvious
A good example of unneeded comments is stating the obvious. Let’s take a look at the following fragment:
// numeric session-id
int sessionId = 0;
// expiry date
Date expiry = new Date();
The comments above are obvious and we might better leave them out.
However, the comment in the next fragment is more dangerous:
// numeric session-id
int sessionTimeoutInSeconds = 3600;
We might have copied it from line 1 of the previous example. However, because we failed to update it, the comment now provides confusion instead of actual information or context.
Finally, the comment in the example explains what the code is doing:
// resets session-id to 99;
function reset() {
sessionId = 99;
}
However, we’d also understand it without comment. And when updating we could forget to update the comment, so better not to write that comment.
5.2. Explaining Unclear Code
Previously, we already discussed that we prefer the self-explanatory code. For example, by using well-chosen names for variables and methods. Or by extracting some code to a properly named variable or method to make it more readable. See for example this fragment of code which we once reviewed:
int timeout = 5; // timeout in seconds
Unfortunately, the comment will only be visible on this line, but not on the places where the variable is used. When we update the name we can get rid of the comment and make the unit clear for every place where we use this timeout:
int timeoutInSeconds = 5;
Sometimes, we also tend to document complex if-statements in the following way:
// if during work hours
if (currentTime.hours >= 9 && currentTime.hours <= 17)
Rewriting this code slightly could both improve the readability of code and make the comment obsolete:
if (isDuringWorkingHours(currentTime))
We’ve seen that sometimes a small improvement to the code could make a comment obsolete. And, at the same time improve the readability of the code.
5.3. Failure to Leverage Version Control Systems
The current usage of version control systems (VCS, such as git, SVN, CVS) makes a group of examples obsolete:
// MvW 2020-08-20 Added method to validate working hours
function isDuringWorkingHours() {
...
}
// code below became obsolete on 2020-08-20
// function isAfterWorkingHours() {
// ...
// }
The VCS tracks all of the examples above. We should rather remove the old code. Also, there’s no reason to track history in the source file. Just make sure to use proper commit messages.
We looked at some examples and possible improvements. In the next section, we’ll suggest some proper usages of comments.
6. Proper Usage
In the previous sections, we explained that comments should be used when the programming language alone isn’t enough to express ourselves. In the next sections, we’ll see some scenarios for which the language itself alone can’t express it.
6.1. Legal Obligations
It could be desired that for legal reasons all files are prefixed with a copyright header. When this is required or desired there’s absolutely no reason not to include it. The benefit is also that this comment is not linked to the actual logic of code. Updating it while updating the code isn’t needed and our brain may filter it away. Most likely it will also be hidden by our IDE.
6.2. Annotating Tasks or Known Defects
Sometimes we might know that something should be changed, however, there’s no time yet to do it. Then, it could make sense to add a TODO or FIXME comment. This will make it clear for others that there’s a task pending. And our IDE might be able to track and list all of them. However, when an issue-tracker such as JIRA is used for our project we should always aim to link the comment to a task on our backlog:
// FIXME CS-122 the method below fails in leap years
function isAfterWorkingHours() {
...
}
6.3. Non-trivial Code
As already introduced before, the developers could have varying experience levels. So when using more complex code constructs such as regular expressions or date parsers. It makes sense to explain the intended behavior.
An example could be the explanation of the regular expression which we’ve shown in section 3. Also, note that such an explanation helps a lot during code-reviews. However, we should make sure that the explanation remains consistent with the logic.
6.4. Explaining Why
A very useful type of comment explains why the code is how it is. Sometimes clear naming alone might not be enough and some context is really needed:
int resultCode = ...;
boolean isSucess = resultCode == 200;
boolean isTemporaryError = resultCode == 503 || resultCode == 504;
// Sometime we receive a temporary error.
// However, in all cases which were researched, results were successfully stored.
// Because of this we will return true in that case.
return (isSucess || isTemporaryError);
These comments could also be a warning. Because the code might look wrong or easy to improve. However, a class could be not thread-safe, and extracting the field to a variable breaks the application. Or maybe a workaround was deployed because users faced problems. A concrete example could be a problem (re)introduced multiple times because the impact does not show directly:
// Don't rewrite this to try-with-resources because that will
// auto-close the socket. The socket is needed every 15 minutes so the
// error might not show directly but this will break the application
try {
... open a socket
} catch (IOException e) {
... handle error
}
7. Conclusion
In this article, we looked at various examples of comments. We looked at some which we should avoid and some other examples for which comments are the best possible solution.
The most important take-away is that by only adding comments that are useful it might be easier to keep them up-to-date.