bran.name

The Truth About Comments

Published

This is the article of a video I’ve made: The First Rule of Comments in Code (10:55).

The problem with comments in your codebase, is that there’s a million ways to do it wrong, and only a few ways to do it right. In other words, it is very hard to write a good comment. That may sound silly: I mean, how is it hard to write a comment, right? First I’ll look at ways in which comments can be unhelpful, then, let’s see what we can do about it.

Unhelpful

Unfortunately, it’s quite easy to end up with unhelpful comments. Let’s look at the most common reasons for that.

Comments can lie

It’s common for comments to get outdated. Comments are like dead code: if it doesn’t run, it doesn’t have to be correct. So sooner or later, it will become incorrect.

At the time of writing, a comment is usually correct. But when the system (the code around it) changes, the comment usually does not change with it. Sometimes the comment is no longer true, because it didn’t change with the implementation. And sometimes the code is moved, but the comment is not moved. You end up with a comment that’s unrelated to it’s context.

Unlike code, you can’t unit-test a comment, you can’t ensure it’s correctness. You don’t have tests and linting to check comments for correctness. So if your comments get a bug, it won’t be noticed.

So what happens when you come across an outdated comment? At best, you spot the lie and are slightly confused for a bit. At worst, you assume it’s true, and go further in the wrong direction.

A comment can be just noise

These types of comments are not lying, they’re correct. But they’re not helpful, because they’re not giving you any new information.

We’ve all seen these types of comments:

// Show next question after some time (milliseconds)
setTimeout(() => setShowingQuestion(true), 1500)

or:

// Done? => Show results
if (isDone(gameState) && showingQuestion) {
  const results = getResults(gameState as Game)
  // ...
}

These comments are absolutely useless, they add no information. Yes, also the 'milliseconds' part, because it can't be seconds, that would we way too long. But mostly, I would argue that setTimeout is assumed knowledge, it's standard library.

Commented code is probably broken

When you’re looking at commented code, you don’t know if it works, how old it is, or why it’s commented. There’s a good chance it doesn’t work anymore if its older than a few weeks.

Commented code will stop working because it hasn’t changed, while it’s environment has changed. The code around it has evolved away from that old state.

And even if it does work, is that how you want to solve the problem right now? Maybe you’ve started using different patterns in your codebase, maybe you’ve learned things in the mean time. The cost of fixing the commented code is probably higher than rewriting the code.

The First Rule

Having seen these ways in which a comment can be (or become) unhelpful, the first rule of comments is Don’t Write Comments.

That may sound extreme, but it’s actually industry practice: many books, professionals and experts have been stating this for decades.

Now there are a few exceptions to the things I’m saying, but I would say it’s true for 95% of the cases. Not all comments are bad. But most are.

Comments only exist in programming languages, because our programming languages are not capable of expressing everything. If you could express it in code, you would. If you can’t express it in code, you unfortunately have to resort to writing a comment.

How to avoid comments

I’ve compiled a list of 6 heuristics you can use to avoid comments.

1. Code that needs a comment, needs to be rewritten

So that it doesn’t need a comment anymore.

This is not always true, but mostly it is. We should always try to put the things in code. When you have the urge to write a comment, I recommend you to think about what information you want to convey, and then try and encode that information in the code itself.

You could for example, rename a variable, because you’ve just learned your variable name wasn’t good enough. It wasn’t good enough, because you needed to add a comment to it.

You want your code to read like a comment, without it having comments.

2. Move information somewhere else

Often this means move the information to a variable/module/class name, sometimes it means move the information to documentation. I took the following example from my video on naming things in code (13:22).

Before:

// available length = max - space - prefix
const availableLength = 8 - 1 - prefix.length

After:

const MAX_LENGTH = 8
const SPACE_LENGTH = 1
const availableLength = MAX_LENGTH - SPACE_LENGTH - prefix.length

Yes, it’s more code, but we’re not paying by the byte here luckily. You can also sometimes move information to your type system:

Before:

// returns null on no match
const indexOf = (array, search) => {
  const result = array.indexOf(search)
  if (result === -1) return null
  return result
}

After, notice the null type:


function indexOf<T = null>(array: Array<T>, search: T) {
  const result = array.indexOf(search)
  if (result === -1) return null
  return result
}

Because the null is in the type signature, TypeScript will complain if you decide to return -1 instead of null. But the comment will become out of date and incorrect.

3. The information is out of date

When you want to write a comment to explain some code, ask yourself: how quickly can this information become out of date? If a comment is just about one line of code, the chance of it being correct is somewhat reasonable. But if a comment is about multiple lines, its quite easy to update those lines, but not update the comment.

So especially when you want to write a comment about multiple lines of code, stop yourself. Ask yourself: how can I put this in code instead?

4. There is no information

It may sound silly, but very often, a comment doesn’t give you new information, because you already have access to the information in the code next to it. I’ve shown this example before:

// Show next question after some time (milliseconds)
setTimeout(() => setShowingQuestion(true), 1500)

The function is named pretty well and setTimeout is a JavaScript standard library function. Do you really need this comment? My answer is no.

If you’ve just written a comment, or when looking at existing comments, ask yourself: what is this actually telling me? What is the information?

If there’s too little, or no information at all: delete it, really.

5. I need to explain this complexity

No, you need to fix the complexity, so you won’t need a comment.

If you can’t, because it’s really that bad or you don't know how to get rid of it, still don't "solve" it with comments: put a markdown file in your source code code next to your file, create actual docs.

Or add it to wherever your docs are located, or create an architecture diagram, which explains it all. A few comments can’t fix or explain complexity. The bad news with complexity is that it requires hard work to fix.

Adding a comment to explain complexity is like putting a band-aid on a lost leg.

Before:

if (!showingQuestion
    && ac.question.code
      === qc.answers[qc.answers.length - 1].code) {
  // ...
}

After:

const isCorrect =
  ac.question.code
    === qc.answers[qc.answers.length - 1].code

if (!showingQuestion && isCorrect) {
  // ...
}

The code has not functionally changed after the refactor, but (again) a name was introduced (the variable isCorrect) to explain what’s happening while lowering the cognitive load of the if statement at the same time.

If the complexity is across multiple files, you need different type of documentation, and better codebase design in the long run. I recommend using Test-driven development and Pair programming to keep the cost of change and general code quality under control, because that’s a long game.

6. If you have the urge to comment code, delete it instead

There are all kinds of reasons of why you might have the urge to comment code.

"I’m not sure I want to delete it yet." For example while you’re still figuring out a solution, just trying out some things.

"We will or might need this in the future, it’ll be easy to bring it back, just uncomment it."

But there is a different solution to all these problems: version control. When using a system like Git, you can look up the full history. If you use a UI tool for Git, it probably supports inspecting the history per file for quick access.

You won’t actually lose anything by deleting, so it’s safe to delete. If you need it back, just use version control.

But the insight is that you probably won’t need it back, because by then, the code will be so out of date that it’ll be easier to start over. And that’s a good thing, because you’ll write better code now.

That’s it. I hope this was helpful. If you’re feeling like speaking up or discussing this, please do so on youtube. Cheers!