How to write comments to improve code clarity

I'm not going to lie - I used to struggle writing comments. I have wasted hours writing comments that aren't helpful to anyone, while leaving out comments that have cost me hours of debugging time.

As we learn to code, we get taught to write comments early. In FreeCodeCamp's curriculum, "How to write JavaScript comments" comes directly after "Introduction to JavaScript".

What we aren't taught is when to write comments. Unfortunately, there are no hard-and-fast rules for knowing when to comment - but I have compiled some guidelines about the types of comments you should be writing, and when to write them.

Documentation Comments

The first type of comments are documentation comments - these are meant to capture your software at a high level, and provide insight into its structure and capabilities.

Importantly, these comments also capture intent, so you are able to compare what the code does against what it was meant to do.

Documentation comments are written in a tool-friendly format so that they can later be extracted into a documentation page. In JavaScript, we tend to use JSDoc for this.

These comments need to be independent, and make sense without reading the code.

How to write a useful documentation comment:

Documentation comments need to capture two broad details about your code.

  1. What does the code do?
  2. How is the code meant to be used?

What does the code do is a high level overview. The most important part of this is that we don't want to know the implementation details - only a high level view.

Much like writing tests in TDD, it's often a good idea to write these comments before you write your code. This helps prevent unnecessary details creeping in (since they don't exist yet).

How the code is meant to be used generally includes things like what you are expecting your inputs to be (e.g. with the JSDoc @param tag), and if applicable, the situations where you would (or wouldn't) use the code.

Even if you aren't intending on exposing your code to others, you should still write these comments – It's unrealistic for everyone to know the entire codebase, so these comments provide useful pointers for your colleagues (and future you).

/**
* Concatenates and returns the values of an array.
* This function has been deprecated. Use Array.concat instead.
*
* @param {array.<string>} arr an array of strings
*
* @return {string} concatenated array
* @deprecated
*/
function concatArray(arr) {
	// ... do something
}

When to write a documentation comment

The rules for writing a documentation comments are quite simple: Write them at the top of your classes to explain what the class represents; and the top of your functions to explain what the function does.

The only time you don't need to write a documentation comment (but might anyway) is when the function overrides a parent's function - you can use the parent function's comments to speak for your function.

Clarification Comments

The second type of comments are clarification comments. These are the ones you're probably more familiar with - these comments are the inline ones that explain the why of your code.

Getting clarification comments right is hard, since there is often no objectively right answer. In general, you want to try and capture details that aren't immediately obvious. This gets can be divided into two types:

  1. To explain why certain code exists
  2. To explain what a confusing piece of code is doing

Here's an example of bad clarification comments:

function concatArray() {
	//Assign values to array
	let arr = [1, 2, 3, 4, 5];
	let total;
	//loop through values
	for (let i = 0; i < arr.length; i++) {
    	//if total doesn't exist, then set a value, otherwise add
        //them together
        total = total ? arr[i] : total + arr[i];
	}
    //return the total
    return total
}

How do we know these are bad comments? Our understanding of the code does not change if the comments are removed. It doesn't require the extra explanation.

Let's contrast it with something like this:

function addSetEntry(set, value) {
	// Don't return set.add because it's not chainable in IE11
	set.add(value);
	return set;
}

This comment isn't exactly riveting, but it does provide information we don't already have. If we come back later, we're going to reconsider the "improvement" of directly returning set.add(value).

How to write a useful clarification comment

The way to make a clarification comment useful is super trivial: put in the missing information. The trick with is knowing when the missing information is good enough to warrant a comment.

When to write a comment to explain why code exists

You typically don't write these on your first attempt, unless you used a non-obvious solution the first time. The easy rule for when you should be writing these comments is whenever you do something that you wouldn't have normally done - an extra piece of code for a bug fix; your second attempt at a solution etc.

/* 
don't use the global isFinite() because it returns true for null values
*/
Number.isFinite(value)
/* A binary search turned out to be slower than the Boyer-Moore algorithm
   for the data sets of interest, thus we have used the more complex, but
   faster method even though this problem does not at first seem amenable
   to a string search technique. */

In truth, these are the hardest comments to get right, but they are life savers. You don't tend to write too many of these, so you should definitely write these whenever you can.

If you aren't sure, write the comment - you can always remove it later.

When to write a comment to explain confusing code

In an ideal world, the best time to write a comment explaining confusing code is never. The far-and-away most useful way to write this comment is to refactor your code so it doesn't need a comment.

Take this example:

function calculateDirection() {
	// .. some code here
	let directionCode = 0; // none
	if (x > 0 && position != x) {
		if (0 > x - position) {
			directionCode = 1; //left
		} else if (0 < x - position) {
			directionCode = 2; //right
		}
	} 
	// .. some more code
}

This code is confusing, no doubt about it. But rather than do a patch-job with comments, we are able to refactor it.

const DIRECTION = {
	NONE: 0,
	LEFT: 1,
	RIGHT: 2
};

function calculateDirection() {
	// .. some code here

    let directionCode = DIRECTION.NONE;
	if (x > position) {
        directionCode = DIRECTION.LEFT;
    } else if (x < position) {
        directionCode = DIRECTION.RIGHT;
    }
	
	// .. some more code
}

Or if you prefer something more compact:

const DIRECTION = {
	NONE: 0,
	LEFT: 1,
	RIGHT: 2
};

function calculateDirection() {
	// .. some code here

    let directionCode = x > position ? 	DIRECTION.LEFT :
						x < position ? 	DIRECTION.RIGHT :
                        				DIRECTION.NONE;
    
	
	// .. some more code
}

Sadly, this isn't an ideal world, and you can't always refactor your code to be perfectly understandable.

So the real answer about when to put a comment in is when the code is hard to read. If someone else can't tell what your block of code does in ~5 seconds, it needs a comment.

The real trick here is that when you've just written the code, you are the worst person to tell if something is hard to read. You need to put yourself in your teammates shoes when deciding whether to write a comment.

As before, err on the side of adding the comment. It's far better that you have an unnecessary comment than code that needs a comment, but doesn't have one.