“Every comment represents a failure to make the code self explanatory.”
One of the better-known sentiments from Uncle Bob’s Clean Code is that comments are bad*. More precisely, the idea is that comments are a last resort option to try and mitigate “bad code”.
Sometimes this seems to lead to a logical fallacy that goes like this: “Comments are a sign of bad code, and my code can’t be bad, so I won’t write comments”. This can progress to an even worse line of thinking: “Comments are for bad code, therefore if I don’t write comments, then my code can’t be bad”.
*Uncle Bob himself does not claim that comments are always a bad thing, as you can see in the linked article above and in this summary. However, I would go further and suggest that code comments are generally positive and rarely a bad thing. In any case, the “comments are bad” meme seems to have taken hold in a lot of places.
Mental states are ephemeral
“Debugging is twice as hard as writing a program in the first place. So if you’re as clever as you can be when you write it, how will you ever debug it?”
Figuring out how to solve a problem and implementing that solution can be straightforward or somewhat challenging, but in either case, your mental state while doing so holds a lot of contextual information about the problem and solution.
The clichéd metaphor for what happens next is that it’s like sand falling through your fingers. Even a day later you’ll find yourself lacking some of that context if you come back to the same code, especially if you’ve been working on something else in the interim.
How much worse will it be for someone else to try and build up the same mental context, or yourself in six months’ time?
Comments are a chance to try and preserve some of this context at exactly the point where it is relevant. The structure and naming of your implementation code is also important, but it cannot replace the assistance of comments written in a language “designed” for conveying information between human minds.
Your code is not self-documenting (even to yourself)
The term “self-documenting code” has become a bit of an in-joke among software developers, and rightly so. Stereotypically, it’s associated with oblivious team-members who inflict their idiosyncratic and incomprehensible code on others while proclaiming that it is self-documenting.
There’s an implication with “self-documenting code” that if you don’t understand it, it’s your fault and not the fault of the code.
If that’s true, then what is the point of using modern, higher-level languages other than to make code manageable and readable for humans? The machine understands machine code and does not care about how understandable the source code was. We could just write machine code and blame it on the human reader if they find it hard to follow.
Imagine a textbook full of “self-documenting diagrams”, furniture with “self-documenting assembly”, or a book of “self-documenting recipes”. These would be nice if they worked, just as self-documenting code would be. They don’t work because in each case we’re trying to transfer knowledge about how something is intended to function, and that is improved with direct explanation.
On that basis, we should use every tool we have to try and make the source code easy to understand. Comments are part of that toolbox.
Comments are part of good code / both are important
In case it needs pointing out, I’m definitely not suggesting that huge methods and god classes with a lot of comments are the way to go.
Someone always points out that instead of writing a comment saying “Handle issue where the DodgyAnalytics.io API sometimes sends us malformed data”, you should break out a method explaining that in the name.
Absolutely break out a method called
handleDodgyAnalyticsMalformedData(). Alternatively, break out a class called
DodgyAnalyticsMalformedDataHandler, or have a
DodgyAnalyticsResponse interface and implement it with a
MalformedDodgyAnalyticsResponseDecorator that wraps the problem up so other code doesn’t have to know about it.
Those are all definitely options you should consider, and are probably better than just fixing the malformed data inline and chucking a comment on it. But if you’ve broken out well-named methods, classes or interfaces, put comments on them explaining what they’re for:
/** * This decorator detects and fixes the issue where the DodgyAnalytics.io API * sends us malformed data due to blah blah blah. * * This issue has been raised with them here: issuetracker.dodgyanalytics.io/123 * * The responses are supposed look like this: docs.dodgyanalytics.io/foobar * * See relevant tests here: @SomeNameSpace.SomeTestClass */
The comment does not mean the code is bad. It will be helpful to anyone else investigating a bug in this area of the codebase in six months’ time. It makes the code more grep-able. It helps people figure out the context and get productive more quickly.
Being anti-comments is being anti-pragmatic
Pragmatism is a good quality in software development. If you ask software developers what their job is, a bad answer is “my job is to write code”. As the maxim goes, our job is not to write code. Our job is to solve problems and improve things for ourselves and others.
Fixating on the code itself can lead to this kind of anti-pragmatic thinking. If the code is all that exists, then we’d better make it pristine. There’s something almost arrogant and pretentious about believing the code you’re writing can be a testament to cleanliness and correctness. High opinions of the code are probably not objective, and even if they were, getting it from 95% to 99% quality (if that can be measured) will consume vast amounts of effort that we should consider spending elsewhere.
We’re not writing a novel for publication, we’re trying to make something useful. Code quality is a necessary part of that, but it is not the whole.
If your code doesn’t need comments, why does it need tests?
Hopefully you write tests for your code and believe that automated testing is important. One of the purposes of tests is to function as a kind of documentation of the test subject. Looking at tests is often helpful for figuring out how things are supposed to work. Aren’t comments important in the same way?
Similarly, another crucial idea with automated testing is the recognition that we are fallible and likely to make mistakes in our implementation. Tests add a layer of confirmation that the implementation is correct. If you can be fallible in the implementation of functionality, can’t you also be fallible in making code as clear and readable as possible? Comments can add a layer of contextual understanding to remedy this.
Finally, automated tests serve to increase confidence that some functionality still works correctly even after the original development context has been forgotten. Tests will identify if something is later broken without you needing to remember why or how it used to work. Comments can be similar to this – the original knowledge might be forgotten, but they can help to preserve it and point you in the right direction when you come back to the code.
Even out of date comments can be helpful
One common objection to comments (and even documentation in general) is that they get out of date, and are then worse than no comments at all. I would say that’s a false line of reasoning.
Should we not write any code at all because the code will get out of date with the requirements? (Note that not writing any code can be a valid solution that is often overlooked.)
Even an out of date comment adds more information than having no comments at all. “Wrong” information is still information that can be used for figuring things out.
If you’re trying to make a chocolate cake, having a recipe for a coffee cake would be better than nothing; you can at least take some hints from it. Similarly, it’s useful to know that a piece of code at least used to solve the issue where bad data from the third party API broke this workflow, even though we’re now solving that issue earlier in the pipeline. You don’t have to take the comments as gospel; they can be nudges in the right direction or insights into what was going on at the time the code was written.
Comments don’t have to just be plain text. Including links is often beneficial, for example:
- Relevant Stackoverflow and Github issues,
- Links to more detailed docs,
- Links to tools such as a Regex helper for a given regex,
And so on. I would say, if in doubt, comment it. It might help you or someone else later, and it preserves useful information in context.
Comment “other people’s” code
“Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code.”
Another Uncle Bob quote, usually paraphrased as “code is read more than it is written”.
Because we spend so much time reading code, make sure that the hard-earned knowledge from that doesn’t disappear into the ether. Put your learnings into a comment and preserve them. The code is a living document, and these additions keep it healthier for everyone working on it.
To reiterate, comments are by no means the sole way of doing this, but they are a helpful addition. Another useful approach is exploratory testing, again showing some of the links between commenting and testing.