A recent conversation came up that kicked me in the “conventional wisdom (or a straw-man version) says X, but I, as a relative newcomer to the field, oppose that” basket. If you follow the Entropy Arbitrage newsletter, you might know that I hate those discussions. When I started in programming, I had many of those same misguided opinions, but people (rightly) mostly considered me a loudmouth with a lot more to learn, and gave me the space to do so. By contrast, people who sound off like this today “go viral,” landing interviews and publishing deals to spread misinformation.

Seen from behind, two people, identified as women of color, review Ruby code on a laptop

Yes, we had the Internet, but not much advertising, so nobody looked to gin up controversies for clicks…

I have decided that I should occasionally push back on this evolution. I can’t stop people from giving a large platform to someone who says “I don’t see any problem with using GOTO statements for all my flow control,” but I can offer another view that someone might find useful.

To make myself clear: My opinions don’t come from some devious consensus. They come from decades of experience, after starting with full confidence that I would change the industry with my outsider thinking.

In this case, we’ll look at the (alleged) common wisdom that “good code” doesn’t need a developer to add comments, and the insistence that the people saying that have it all wrong, and people should comment their code.

Commenting on Comments

Superficially, when I talk about comments, I mean a syntactic gimmick to allow a developer to write arbitrary (non-programmatic) text into a program. That text generally has no effect on the program’s operation. Rather, the comments sit there as notes for future developers, rather than as instructions for the computer.

Because different developers have different interests, and because nobody usually bothers to standardize what goes into a comment, those comments see a wide variety of uses.

You might see comments that state facts, like how often an outside service checks for a message. Others might express uncertainty that the code will work under certain conditions. Occasionally, a comment has nothing to do with the code, leaving developers to wonder about the connection.

The “Controversy”

If people want to push back on the wisdom that we shouldn’t write comments, we should find out who actually supports and repeats this wisdom. And if we search—don’t worry, I won’t subject you to examples—we find two categories.

  • A larger group wants to make a point about seeing documentation as a task to handle before writing code, not after. Technically, they still want you to write comments, but see it as a promise that the code that you write should keep.
  • A tiny…well, not quite a group, as such, since they don’t seem connected to each other, but they believe that they write code so straightforward that it doesn’t need comments. This tiny “movement” of people who you’ve probably never heard of appears to most directly connect to what people react against, framing them as an orthodoxy.

Much larger than both of those groups, we have people who support comments as a normal part of a program. When I say that commenters dwarf everybody else, I mean it. No (serious) programming language lacks an inbuilt way to write comments. No IDE flags your comments as unnecessary. And no static-checker suggests that you delete your comments. Several programming languages and environments even try to shift the focus to the comments, so-called literate programming systems.

If anybody really took a “don’t write comments” edict seriously, some major compiler or style-enforcer would at least have an option to reject code with comments, plain and simple.

However, both small groups—and many people outside those groups—misunderstand what comments can do.

Concessions

Now that I have mostly provided the various positions, I should say that most comments unfortunately exist purely to admit failure. They might do so literally, saying something like “I don’t know how this subroutine works, but it does, so don’t touch it.” Or the comment might admit failure by repeating what the code already says, in English, because the writer failed to write clearer code.

Nobody should ever write those comments. If you don’t know why your code works, figure it out, and ask for help, if you need it. If you genuinely need to explain how the code works, then the code warrants a rewrite to more clearly present itself, probably with fewer fancy features of the programming language.

These sorts of comments cause problems, because they become part of the maintenance cycle. Every time someone updates the code, they need to review the comment, to make sure that it still represents the code. And if you need to update the same information twice in the same file, then you have probably done something wrong.

While nobody should write this variety of comments, though, deadlines might mean pushing sub-standard code out the door, with the intent of cleaning it up later. And so while it shouldn’t happen…it probably will. I should never publish a post with a typo in it, but these things happen. “Pobody’s nerfect,” as the saying goes…

Still, if you can avoid the failure, then your comments never need to admit that failure. If you can rewrite code to avoid a comment, then you should rewrite it, because the comment didn’t add anything useful.

This, I think, focuses us on our central problem. When many people talk about “commenting code,” they mean reviewing code after writing it, and saying something like “this line might confuse my colleagues.” And that leads to comments like any of the following.

  • This assigns the current total to the total variable.
  • Assign the value of x to y.
  • Loop over the array indices.
  • Increment i by one.

Again, none of these comments serve a purpose that the code won’t already serve. And at the risk of repeating myself—thematically appropriate, I think—someone needs to update the comments, if the code changes, or risk their confusing someone else.

Rather than say that you should or shouldn’t write comments, I’ll suggest that you should only write a comment if it adds new information.

Comment Your Project

Rather than commenting “code,” you really want to comment on the project, from within the code. You want to write the comments that explain why, not how. The code already describes how to do the work.

Let’s look at some quick examples of what might make a better comment.

  • Why did you write this passage? Specifically, if business conditions change, what changes would lead a future developer to delete the code?
  • Does the code implement some standard, such as an algorithm, a design pattern, or a specification? If so, which one? Where can your successor find the documentation on it? Did you modify that design at all?
  • What alternatives did you consider and dismiss? If the current solution causes problems in the future, which alternative(s) should your successor consider first?
  • What trade-offs did you make in this design? What technical debt did you accrue to meet the deadline? We all need to make expedient choices, sometimes, and debt can enable a person to accomplish more with their resources. But if you don’t highlight those choices, they become more difficult to find, when the team ultimately needs to repay the debt.
  • Did you adapt the code from another source? Does that code require crediting the author or other terms?
  • Do any edge cases exist that the code deliberately doesn’t bother to cover? Why did you choose them?
  • Can you cite a passage in a design document describing this feature or significant change on some level?

The last item brings up an interesting point, that comments no longer exist exclusively in code. The description for the feature might sit in an issue-tracking ticket, along with a discussion of the trade-offs involved. And generally, you don’t reference tickets from code, because every code change should connect to a ticket, and so leaving a comment for every change would quickly become unreadable.

However, since almost every project uses some form of version control, your commit message should absolutely reference the relevant ticket, which joins the two systems through metadata.

…Don’t Comment “the Code”

If, instead of covering that kind of ground, your comment explains the syntax, then you should rewrite the code instead of writing the comment. Let’s take a quick (hypothetical) example.

x = y = z + a = b(c, d *= e, f += g, h ? b : i ? j : k);

That probably compiles on a standard C compiler—assuming that the b() function exists—but it takes longer for each developer to figure out how to read it than it would for me to separate the assignment statements, change the conditional operators to if statements with additional variables, and give everything real names.

I could write a comment explaining the flow, sure, but you’d spend about as much time reading it as deciphering the ugly code. It would also take me a while to write that comment. And if the comment doesn’t match the code, how do you determine which one matches the software’s requirements?

By contrast, let’s take another example, really the same code, rewritten.

l = k;

if (i) {
  l = j;
}

m = l;

if (h) {
  m = b;
}

a = b(c, d, f, m);
y = z + a;
x = y;
d = d * e;
f = f + g;

Not only does this (ignoring the shoddy variable names) seem clearer, but it exposes a potential bug in the original: Early specifications for C didn’t define a strict order of execution for those “assignment operations” (*= and +=) compared to a function call, meaning that different compilers will produce different behavior. Separating them out forces the developer to make the order explicit. They either change the value before the function call or after.

Speaking of variable names, if the comment explains what the variable name or a constant means, rename the variable, instead. Idiomatic names—like using i, j, and k for loop counters—can feel justified due to their narrow scopes, but a name like userIndex might make iterating through users much clearer.

Again, those comments describe the code, and we shouldn’t force any developer to read them. Rather than write such comments, refactor your code in a way that it doesn’t need a comment.

Fancy Features

Keep in mind that I don’t say this to rail against modern conveniences in programming languages or complex code. If you need the Elvis operator or a safe navigation operator, use it. You can assume that your colleagues understand new language features, unless management has told you otherwise—for example, targeting a certain version of a language, because a client can’t install later versions of the runtime—and these features almost always improve readability, once everybody grows accustomed to them. Most of them exist to improve readability.

However, when you see yourself using many language features in one line, and you don’t recognize it as an idiom from that language’s community, that tends to indicate code that will confuse people. If you feel inclined to write a comment about it, think about reorganizing it, instead. Sometimes, formatting with and otherwise adding some blank-space will work well enough, but other instances require breaking things into multiple statements. Trust me, if you don’t have an actual count of the bytes of memory that your code uses, then you can absolutely afford a few extra variables.

Context Matters

It also helps to consider what you need to do, in the larger project. Consider the following example.

function quicksort(array) {
  if (array.length <= 1) {
    return array;
  }

  var left = []
  var right = []
  var pivot = array.shift();

  while (array.length) {
    if (array[0] < pivot) {
      left.push(array.shift());
    } else {
      right.push(array.shift());
    }
  }

  return quicksort(left)
    .concat(pivot)
    .concat(quicksort(right));
}

Here, the need for specific comments depends on the audience.

If we plan to publish this as example code for beginners, to teach the algorithm, each section of code—the four parts separated by blank lines—should warrant some explanation. The first block says that an empty array represents an always-sorted degenerate case. The second initializes data. The third implements the sort, by iteratively deciding where to place the next element. And finally, we recursively assemble the sorted result.

If we run this code to sort things, we don’t need any comments. The code sorts. It has a label of quicksort, which people can look up to learn the algorithm.

However, most critically, as part of a larger application, the comment should explain why we have implemented a quicksort at all, instead of using the standard library’s .sort(), which usually implements a quicksort of its own. What does the project gain in these eighteen lines of code—that people need to test and maintain for the life of the software—that the project didn’t already have with less work invested into it?

I suspect that these differing contexts explain why people feel conflicted about commenting their code: When we learn to write software, writers fill textbooks with terrible comments…in a professional setting. For education, a reminder that i++ means “increment i by one, after the statement completes,” makes sense. The reader may have forgotten, because they probably don’t write code every day, yet.

However, in an office of software engineers, that reminder no longer has value, but still asks for the reader’s attention. In that context, the useful comment might look something like, “we increment i here, because this situation counts extra toward our total, as seen in…”

In some cases, even that might demand too much attention for too little benefit, and so the “right comment” might look like a single description of the algorithm, or a ticket/specification where readers can find the description.

Mostly, though, the best comment that you can write finishes a sentence that looks like, “I wrote this code instead of the alternatives, because…”


Credits: The header image is wocintech (microsoft) — 62 by WOCinTech Chat, made available under the terms of the Creative Commons Attribution 2.0 Generic license.