The importance of comments - Part 3
This is the third and last post discussing the value of comments in code.
In the first post we discussed the problems of the widespread dogmatic “clean code” interpretation that you must not write any comments. We also discussed that we should not document the “how” as the code already does it.
In the second post we discussed the value of documenting “what” for the readers and users of the code.
In this post we will first discuss some typical counter-arguments regarding the “what”-documentation, plus some additional considerations how to find the right balance. Then, we will discuss how to document the “why”. Finally, we will bring it all together.
The usual counter-arguments
In the last post, I gave the following recommendation regarding documenting the “what”:
Use building block header comments to document the “what”.
(Building blocks being methods, functions, classes, modules, …)
The usual “clean coder” counter-arguments are both flawed from my experience:
Good method names are self-documenting – This works nicely for the toy examples that are usually presented. But in a code base consisting of hundreds and thousands of methods, this idea hits its limits. It is simply not possible to find a name for all methods that describes what the method does in a perfectly unambiguous way.
There might be a similar method that still does things in a bit different way for good reasons. There might be a relevant side effect that you need to know if you use the method. There might be some constraints for the parameters that are not obvious at first sight. The method is the entry point for some complex behavior, calling itself many other methods directly or indirectly. And so on.
Thus, while you should work hard to find good names for your methods, you will neither be able nor want to put everything you need to know about the method in its name.
The unit tests are the method documentation – Sorry to be harsh, but that is complete nonsense. You should test your stuff in an automated way. Totally agree. But tests are not a replacement for documenting a contract. Simply pointing me to the tests leaves me with the same problem I had before: I have to read lots of code that is irrelevant for solving my problem. I only want to know what the method does. I do not want to read and understand dozens of tests for that.
Just imagine the whole standard library of your preferred programming language were only documented via unit tests. Imagine, e.g., the whole Java SE API documentation would only be available as unit tests. If you would like to figure out which method to use to read your file, you first would have to read through hundreds or thousands of unit tests to (hopefully) figure out which method to use. Just let the whole idea sink in for a minute and it should become obvious why I do not buy into it …
Thus, while you definitely should automate your tests and aim for a reasonable 1 test coverage, automated tests are not a replacement for documenting the “what” of a building block.
In the end it is a time saved vs. time wasted consideration. It is about writers and readers of code. As code is read a lot more often than written, commenting what the code does can save tons of time and avoid many bugs.
Let me add a few additional considerations before moving on:
Do you need to document every method? – I do not think so. The typical counter-argument of documentation shirkers is: “Documenting all those getters and setter is stupid.” And from that extreme case they derive that documenting the “what” in general is stupid.
First of all, plain getters and setters that do not do anything but retrieving and modifying a private property without any additional behavior are the actual stupid idea. This is pseudo-encapsulation. In such a case, just create a honest interface and make the property public instead of “hiding” it behind a pseudo-encapsulation.
Besides that I think it is okay not to document every single method. There are methods that indeed become obvious by finding a good name for it. There are also a lot of methods that are obvious when briefly looking at it. I think, it is okay not to write any additional comments for them. You look at them. You know what they do. Fair enough.
But many methods are not that simple. Especially interface methods that are used by other parts of the code tend to encapsulate non-trivial behavior. Even if the method itself is quite simple, it calls other methods which in turn call methods, etc., and all the methods called implement quite some non-trivial behavior together.
Therefore interface methods should always have a short contract, a short documentation what they do, what they expect as input (including potential constraints), what they return, if there are any side effects and what else is relevant to know. This makes it a lot easier to use the method, i.e., it drastically improves the developer experience. It avoids unintentional bugs and it makes reading code that uses the method a lot easier.
What about auto-generated comments? – In general, I think auto-generated comments are a totally stupid idea. Better do not have any comments than tons of auto-generated comments.
It is okay to use some IDE magic to generate a comment stub. But you need to fill it own your own.
Remember: The code is the “how”. A comment generator only has the code as input. Thus, comment generators are limited to documenting the “how” – and the “how” should not be documented via comments as it does not add any value.
But comments always become outdated! – Do they? I know that this a very popular argument of the comment shirkers. But in the end it is just a question of discipline. It is a bit like with coffee mugs. Some people leave their mugs wherever they are. Some people bring their used mugs back to the kitchen and put them in the dishwasher.
Arguing that comments are useless because some people lack the discipline to keep them up to date is like arguing that coffee is useless because some people lack the discipline to put the used mugs in the dishwasher. It is a problem of lacking rigor, not a problem of the thing itself. Comments have a huge value if done right as long as people have the will and discipline to write them properly and update them if they change the corresponding code.
Additionally, I have also seen outdated clean-code-ish method names that did not match the behavior of the method because someone changed the implementation of the method without also changing the method name. If you apply the reasoning of not doing something because it can become outdated to method names, you quickly see that the argument leads nowhere.
In the end, it is about diligence. Not having the diligence to check and update the comments when you change code is not an argument against comments. And if you should find an outdated comment: Nobody keeps you from updating it. You would also update outdated code if you see it. So, why not comments?
But my codebase changes so fast. Writing comments doubles my efforts! – First let me doubt that writing comments doubles the efforts. Based on my experience writing comments are a single percentage overhead. Compared to its value for the readers (if done right) that additional effort is extremely well-spent time.
Still, depending on your design approach your code base might be very volatile in the very beginning. Methods and their signatures might change a lot. New methods appear, old ones vanish, all across the course of a few hours. So, if your design approach is sort of “trial and error towards stabilization” (which can be a perfectly fine approach) 2, maybe wait a moment until your implementation starts to “cool down” and stabilizes before adding the contract documentation.
Usually, we talk about a delay of a few hours which is acceptable, especially as no-one else will use that part of the code base anyway as long as it is that volatile.
There would be more things to discuss here, but it has become quite a lot already. Thus, let us move on to the “why”.
Documenting the “Why”
My personal rule of thumb is:
Use inline comments to document the “why”.
Sometimes it is not obvious from reading the code why it was implemented that way. Inline comments are a great way to document those decisions for the future readers of the code. The explanation stands next to the code it is related to – the best place possible for such an explanation.
E.g., I once needed to implement a weird workaround for a buggy database driver that crashed under certain conditions. Without knowing about the bug the code looked like I had several beers too much before I started coding. Every reasonable reader of the code would have had a WTF! experience and immediately “fixed” the code – resulting in a crashing application at runtime.
Thus, I added some inline comments in front of the weird code section, explaining the database driver bug and how my code avoided crashes. I also noted the number of the bug report and the database driver version we used at that time. This way readers were able to check if the bug was fixed with a newer driver version and they could remove the weird workaround.
This is a quite extreme example, but you will encounter quite some situations in your coding life when your decisions are not obvious to the readers of your code. Use some inline comments to explain the rationale. Your readers will be really thankful for it.
But to be clear: This is not a carte blanche to write overly complicated code in a negligent way!
Work hard to write clear and understandable code!
This is only about non-obvious rationales that cannot be transported via the code. The code is the “how”. Often the “why” is obvious enough that it can be derived easily from the “how”. But if not, spend a few lines of inline comments to explain the “why”. It makes maintaining and changing the code a lot easier and avoids a lot of involuntary bugs.
Used correctly, comments have huge value. They can save a lot of time of the future readers of the code. They can avoid a lot of involuntary future bugs. As code is read orders of magnitude more often than written, the effort of writing a good comment quickly pays for itself.
Not writing comments at all inhibits this potential value. Writing the wrong types of comments also does not create any value. My personal rules of thumb for writing comments are:
Use inline comments to document the “why”.
Use building block header comments to document the “what”. (Building blocks being methods, functions, classes, modules, …)
Do not comment the “how”. The code should be the documentation of the “how”.
These recommendations helped at least me to decide when to write comments and when not to write comments – and where to put what. It also saved my future selfs a lot of time when I came back to my code after working on something else for a while. And I can only guess how much time it saved the other readers and users of my code.
I would like to close this little series with a little aha-experience of mine that made me think differently about comments:
I was about to leave a company. While working there, I maintained several 100.000 lines of code, I inherited from other people that maintained them before. The code was very poorly documented when I inherited it. While working through the code I inherited and trying to making sense of it I then added comments for me. It was a large code base and I did not want to rediscover the workings of the code repeatedly. I simply captured my learnings for my future self. 3
Also after the initial takeover I regularly added some comments whenever I stumbled upon something that confused me in the code base. Again, I captured my learnings for my future self.
Before I left the company, a future maintainer of my code was determined. I went to him, showed him the repositories and asked him to have an initial look at it for the next 2 or 3 weeks. After that time I wanted to sit down with him and answer his questions.
When I approached him after 3 weeks and asked him when we should sit down, he just replied: “We do not need to sit down. I do not have any questions. Everything I needed to know became clear from the comments.”
Well, that saved me about 4 weeks I had planned for the handover. This was a more than pleasant side effect of the comments I initially wrote for myself – not to mention the time the comments saved me while maintaining the code.
I hope, I gave you a few ideas that will help you to shape how you will use comments in the future.
I do not want to discuss here, how much testing is “reasonable”. As with almost everything, I think the sweet spot lies between the two extremes. I also think that the answer is highly context-dependent. So, any general rule does not make sense for me. In the end, testing is a risk management measure. Thus, it is about context, probabilities and criticality. ↩︎
E.g., if you use TDD, your methods and their signatures can change a lot in the initial phase of creating some new behavior. Still, stable parts start to emerge soon. ↩︎
Before anyone claims I should have improved the code instead of writing comments: Most of the code was quite straightforward and simple to read. The problem were many special cases the code handled. Most of the code processed data files. Some of the data files had quirks for historical reasons. Handling these quirks often resulted in non-obvious, weird, “one beer too much” code. The hard part was to figure out if the code was buggy, a leftover no longer used or still relevant. If it still was relevant, I documented it to immediately know it the next time. ↩︎