A Subtle Art Of Writing Good Code Comments

A Subtle Art Of Writing Good Code Comments

Today I want to explore somewhat overlooked topic of code commentaries. It seems like a simple topic, yet it is the one that sometimes invokes the hottest water cooler arguments! But why do we debate about it, why do we understand it differently? What is a bad code comment and makes a good code commentary? Why do we write code comments in the first place?

Some argue that code must be self-explanatory and there must be no code comments at all, that code comments are a sign of badly written code. On the opposite side of the spectrum is “comment every line” camp, those who insist on extensive comments of every piece of code. But where is the right balance?

Over the years everyone has seen lots of code comments. Some of them were really useful, others were useless, while some were utterly harmful. Looking at those, everyone has developed his vision of what code comments should look like.

Let’s take a look at some examples.

Useless comments

Sometimes code comments doen’t bring any value. They don’t seem to add any additional information and seem to be only exploding line count.

For example:

  • A comment that states the obvious
/* Sets price */
public void setPrice(int price) {...}
Such comment does not bring any additional value. It just re-states something that is already obvious from a function name. 
  • Comments on every line
// validate request

//extract data from request
SomeData data = request.getData();

//Process data
Such style of commenting is a variation of the previous one. It usually starts with good intent to explain every step of the process. But in this case using good naming convention and [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle) gets you all the way there. Splitting big chunk of code into smaller functions with clear name and sole purpose gives not only provides you with  such step-by-step explanation, but makes comments useless.
  • Arrange-Act-Assert comments in unit tests
public void testGiveNullReturnsEmpty(){
	Converter converter = createDefaultConverter();

	Optional<SomeComversionResult> result = converter.convert(null);

Following Arrange-Act-Assert structure of unit tests is a great idea. But conventionally this separation in code is achieved by using a new line between sections. This is a common practice and anyone familiar with this pattern will recognize it immegiately. Stating them explicitly does not add any clarify, just adds clutter.

Harmful comments

Sometimes code comments can bring more harm then good. They can confuse or mislead.

  • Clarification of what variables
// time delta
private long t_d;
Typing "t_d" might take less keystrokes, yes. But it requires much more mental capacity to process! Every time you read variable names like that, you need to make mental conversion into what it really means. And if there are multiple variables like that in a piece of code, especially with similar meanings, the amount of cognitive load grows exponentially. Why not call variable "timeDelta" in the first place? 
  • Outdated comment
// Returns time in UTC
public DataTime getLocalTime() {
	return localTime;
The biggest problem with comments is keeping them up to date when code evolves. It often happens that while code is changed, comments are unloved and forgotten. As a result, code might have rich comments that are artifacts of the past, showing what used to be there but no longer present. It is absolutely useless and very harmful.
  • To-Do comment
// This is a temporary hack. Will be removed at the next iteration.
// This code should have never been committed.
Such comment is and indication of a major technical debt. I was surprised to see such thing in a major piece of code that constitutes the core of business logic. Especially, noticing the fact that it has been committed over 8 years ago. We all have deadlines and sometimes taking shortcut is the only solution. But important here is having a discipline and pay off technical debt a soon as possible. Otherwise, there will be other shortcuts applied on top of that shortcut and tech debt will grow like a snowball. Bottom line - To Do items don't belong to code, they belong to a Scrum board.
  • Implementation details comment
// Calculates shortest path between points using
// A* algorithm. It starts with ...
public List<Node> calculatePath(...) {}
Implementations change. Interfaces persists. Using comments to describe implementation details is like planing a timebomb. This comment *will* become obsolete, it is only a question of when.
  • Misleading comment
// Does one thing
public void doSomething() {
Such comments claim to clarify what a piece of code does, when in fact it does something else. Usually it is also a leftover comment that was not updated as code evolved. 
  • No comments at all
class Record {
	private String marketplaceId;
	private String merchantId;	
	private String isbn;
	private Money retailPrice;
	private List<Schedule> schedule;
Sometimes you can stumble upon classes with generic names and a collection of fields that don't tell anything. It is unclear what such classes represent, why they exist and what those fields mean. 
  • Documentation generation
 * Sets the value of the field "isbn" to be used for the constructed object.
 * @param isbn
 *   The value of the "isbn" field.
 * @return
 *   This builder.
public Builder withISBN(String isbn) {
  this.isbn = isbn;
  return this;
I am likely to be in minority when it comes to comments that are used to  generate documentation (for example, JavaDoc). While I can understand the  good intention, I find it having a big downside. First, this is just a comment, it is used for generation of documentation, but it still  needs to be updated to keep up with code just any other comment does. Second, sometimes such  comments grow so big that they hide the actual code. I have seen some [PoJos](https://en.wikipedia.org/wiki/Plain_old_Java_object),  where documentation comments took 75% of space, making it hard to  see the code itself.

Comments that create value

Some comments can useful and bring actual value. They can explain things and help to avoid ambiguity.

  • Clarify the intent
// A database record for a book. Core book entity to be used to persist any
// data associated with a book. 
class BookRecord {...}
Such comment does not go deep into details, it does not say anything about  implementation or type of database to be used, or about class internals.  All those things might and will change. Comment will stay relevant, its  goal is to clarify why this class exists, not what it does or how.
  • Provide meaning
// Book discounts. Maps book ISBN -> discounts available for the book
Map<String, List<Discount>> discounts;
Programming languages give limited tools to express the full spectrum of human ideas. Sometimes comments can be used to express ideas that we cannot express in code. For example, using String as a map key in example above does not give any context. But adding "ISBN" as its meaning makes it easier to build mental model. This in turn, facilitates development process and helps to avoid errors.

Start with why

There are multiple opinions on code comments. But let’s ask ourselves the most important question. Why do we write code comments? What is their function, why they exist? My own answer is that comments exist to express ideas that we cannot express with code. It is easy to express alorithms in code, states, sequences. In other words, it is easy to express what to do. Also it is easy to express how to do things. But what programming languages don’t allow us, is to tell why things exist. Why do we do things one way and not another. If we look back at examples above, it is easy to see a pattern. Comments that bring value and stay relevant over time complement code, helps to express ideas. Say why. On the other hand, the comments that bring nothing, or bring more harm than good, get into implementation details, express things that we express with code. In other words, they say what and how.

So I believe that the subtle art of writing good code comments that live long and bring value, is to use comments to say why, not what or how.

See Also

How Code Coverage Lies To You

In my previous article I talked about why it is a good idea to write unit tests. Now I want to talk about a different question - how much unit tests to write?

Why write unit tests?

In this article I want to talk about unit testing. Why do we write them? What do we expect from them? I have heard many different opinions, sometimes quite opposite.

Does It Mean You Are Doing Object-Oriented Programming If You Write Classes?

I remember a job interview that I had in my early days as a software engineer. I was asked what is the Object-Oriented Programming (OOP). I tried to remember things I learned at a University and murmured something about encapsulation, inheritance, and polymorpshism.

comments powered by Disqus