Programming comments are a critical, and often overlooked, part of the development process. Comments may not be exciting enough to have certifications and training classes dedicated to them, but in many ways the difference between a good codebase and an excellent codebase can come down to the quality of the comments.

Comments increase the value of your code. Compared to comments, though, we often spend more time learning methodologies like Scrum and Agile; functional and object oriented programming; test driven development and refactoring.

Yet comments are critical in ways that methodologies aren’t. In the real world, we rarely spend years working on the same project; we often switch from one program to another multiple times in a week! Each program has its own style, design patterns, and idiosyncracies; comments help us learn them. Each team has its own philosophy, development lifecycle, shared knowledge, and communication style; comments help us make progress even if senior engineers aren’t available to explain something.

Comments vary widely. Unless you have interned for me, I’m pretty sure your style of comments doesn’t match mine. Better known programmers than myself have written about comments, including perhaps most famously Donald Knuth’s argument against them. It’s always worth reading about what other people think: we become better programmers, whether we use another person’s ideas or not, simply by knowing about them and understanding their reasons.

The Value of Comments

The primary value of comments is their ability to teach us about our codebase. This kind of shared knowledge helps us avoid bugs that previous programmers anticipated. It helps us switch from one program to another rapidly. Really good quality comments can reduce the amount of meetings we have – by helping others get answers directly from reading the software rather than scheduling time on your calendar.

But I find the most incredible feature of comments is this: Writing lots of comments makes me think through my code. I embark on a project with the goal of writing one comment for every three lines of code, a level of detail that most people find silly, but a level that I find helps me better explain my software architecture both to myself and to someone reading through my work.

I often hear the objection, “I can’t waste 25% of my time writing comments!” Of course, nobody will force you to write a specific number of comments. But in my experience, I spend only a fraction of my “programming” time actually writing statements; I spend more time staring at an algorithm mentally thinking through its consequences, or deciding to refactor. When I work on code with fewer comments, this “staring” time takes longer. Revisiting an old program with no comments requires me to “re-do” the mental work I did when I looked at it the first time.

That said, let’s thinking about the types of comments we write and how they help us.

Explanatory Comments

Explanations are the most commonly used type of comments. An explanatory comment gives us a “reason” or a “justification” for a particular piece of code. If we write some logic that looks non-obvious, or if we’re doing something that looks at first glance to be wrong or unnecessary, you should add an explanatory comment citing enough information to prove your logic is correct.

Another way in which an explanatory comment can help is illustrating pre-conditions or consequences for your logic. Imagine you’re writing an extremely high performance inner loop function, and you discover that you can save some execution time by assuming a list has already been sorted. Before you make that assumption, write an assertion to verify the sort (put it in a “#ifdef DEBUG”), and write a comment explaining why it helps.

When writing an explanatory comment, you should feel free to write as much text as is required. Sometimes a four or five line comment is necessary to communicate the information to the reader. Don’t stress out about rewriting or polishing the comment; focus on communicating everything that needs to be said.

Here’s an example of an explanatory comment for a very obvious test – checking if a variable is null. But this comment tells us who should have populated the variable (“loaded in ValidationStep”), which could help us track down problems with the object’s configuration. It also says that the variable must be set at this point in the code, and that we can’t just throw an exception for a missing parameter.

 
  //loaded in ValidationStep; if null, then we need to create a new one
 
  if (operationContext.Worksheet == null) {
 
  

Documentation Comments

These comments are generally designed by your language or development environment. Many code editors will automatically provide popup help to a user when they hover their mouse over a function call or code statement; and this type of documentation can be extremely useful as a development and debugging aid.

In many ways, documentation comments are similar to explanatory comments – except that they often use a slightly more formal writing style. When writing documentation, take the time to polish your text, especially for functions that are called often. Shorter text is often better; but don’t eliminate critical information.

This example below is the function header for a CSV output function from my open source CSV library. The text is short and designed to be readable through Visual Studio’s context sensitive help; hopefully with enough detail to answer the obvious questions a user might have when calling this function.

 
  /// <summary>
 
  /// Write the data table to a stream in CSV format
 
  /// </summary>
 
  /// <param name="dt">The data table to write</param>
 
  /// <param name="sw">The stream where the CSV text will be written</param>
 
  /// <param name="save_column_names">True if you wish the first line of the file to have column names</param>
 
  /// <param name="delim">The delimiter (comma, tab, pipe, etc) to separate fields</param>
 
  /// <param name="qual">The text qualifier (double-quote) that encapsulates fields that include delimiters</param>
 
  public static void WriteToStream(this DataTable dt, StreamWriter sw, bool save_column_names, char delim = DEFAULT_DELIMITER, char qual = DEFAULT_QUALIFIER)
 
  

Narrative Comments

Some comments are there to hand-hold the reader through the program’s thought process. Rather than explaining complex logic, the narrative comment is tying together lots of simple logic with a story that helps make the code easy to browse. Narrative comments don’t have lots of detailed technical explanations, but they can be very helpful when you pick up a program for the first time (or return to it after a long absence).

By writing good narrative comments, you can show visually when a function has grown to need refactoring. If your story grows unreadable, often your logic needs to be rethought and simplified. If you write lots and lots of narrative comments explaining boilerplate work, perhaps that overhead can be moved to a shared class or initialization function.

These story-style comments can be very verbose, but they can also help you work in a manner reminiscent of “test-driven development.” Rather than writing your logic first, you can write your comments first – this is why all of my comments come before the code, not the other way around. I write by explaining what I’m going to do, then doing it; and my comments always come with a line before them to make it easy to skim.

For example, here’s a series of “narrative” comments I wrote for a data publishing program. I had snippets that already did most of the heavy lifting and I wanted to refactor it into an automatable program. I started by writing a narrative of how I wanted the program to work; after each of these comments, I left space to fill in with calls to existing snippets of code:

 
  // Did the customer want us to compile the MDB database?
 
  if (BuildMDB) {
 
  
 
      // Next generate SQL statements to update the file
 
  
 
      // Next execute the SQL strings onto the MDB file
 
  }
 
  
 
  // Does the customer want us to deploy the MDB database?
 
  

Blame Comments

We frequently are forced to write code by circumstances we don’t fully understand or business decisions that may seem irrelevant or arbitrary. When we’re forced to fix someone else’s bad logic, or when we receive a trouble ticket to make a requested change, it is helpful to blame someone. Because of this, the difference between a “blame” comment and an “explanatory” comment is that the “blame” comment refers to something outside of the code – maybe a person, a bug number, a business case, or a project design document change.

The blame comment helps a programmer track down an external justification for our logic. If our program does something unexpected or random, and we can’t look into the code to see the reason why, we may need to go to an external source, talk to someone, or find a design doc (why haven’t you posted your design docs on an internal wiki?).

Blaming is not always a negative – in many ways, they can be virtually indistinguishable from a “credit” comment. I would encourage you to write your “blame” comments in a language-neutral fashion: don’t let your anger or frustration seep through, just reference the decision. A future programmer may see your justification and craft a better fix that solves the same problem, or they can go to the right source be able to challenge the decision.

Rich Skorski notes that it’s useful to “blame” someone clearly in the comment, and especially to datestamp the comment. When you either blame or credit someone, that enables people reviewing the code to make decisions on the logic or past experience with the author. In some cases, it can show whether assumptions may have changed since the comment was written, and in other cases, as Bruce Dawson notes, the compiler, language, or toolset may have changed. Labeling the blame allows the reader to determine whether the original assumptions should be revisited.

Some examples of “blame” comments from myself and Bruce Dawson:

 
  DbUtils.CommandTimeOut = 10 * 60; //10 minutes: Ticket #19267: increase timeout to handle large volumes of data
 
  // Work around bug in VC++ 6.0
 
  #pragma optimize(“”, off);
 
  

Wrong Comments

If you write as many comments as I do, you’ll often discover that a comment is incorrect. There are a few different kinds of “wrong,” though. Some comments are wrong because the logic that they explain is flawed. Sometimes the comment is wrong because the business decision used as the source of the logic has been changed since it was written. Other times, the comment is simply mismatched with the code – it doesn’t accurately represent the work the code is doing.

Almost a decade ago, Jeff Atwood wrote a concise article about the problems that lead to comments being wrong or useless – and his guidelines are a good way to learn about how to avoid “wrong” comments.

Still, I find that “wrong” comments can be useful in learning a program. You may want to write a unit test to verify that the code is in fact doing something wrong before you fix it. You can write “blame” comments to attach more detailed justification to the code. Or if the comment writer is simply wrong, you can improve the code’s documentation by fixing the comment.

Wrong comments can be teachable moments too. Imagine that you’re investigating a “wrong” comment, only to discover that the comment is, in fact, correct! Along the way, of course, you probably developed your understanding of a key business process. In this case, you may want to add to the comment, appending your new knowledge to what was already there.

This example below shows a code fragment doing something questionable. The function itself isn’t time sensitive, so there’s no reason for a “sleep” statement. But when you read the comment, you realize why there’s a sleep statement – and this realization should help us fix the function so that the sleep statement isn’t needed!

 
  Thread.Sleep(1001); //because our ack file names are time based, we have to make sure they are unique (by seconds)
 
  

Placeholder Comments

Similar to a “wrong comment” is a placeholder comment, designating something not yet known: when you don’t yet understand something and want to remind the reader of a potential problem. These questions are particularly useful when you’re maintaining an existing program, and you need to start understanding a complex codebase.

When you read through some logic and nearly understand it, it’s worth adding a “placeholder” to indicate what knowledge has yet to be demonstrated. If you don’t know for sure that a condition will be satisfied, or if you need further research to make sure a specific edge case won’t occur, write it in a comment. Some authors find it very useful to label placeholders specifically, with text patterns like “TODO” or “NYI” that one can find easily with a global search.

In this example, the author has decided to expose a member variable, but isn’t certain that the solution is ideal:

 
  // Does it make sense to expose the child objects here if the application needs to retrieve both parent and children?
 
  public IWorksheetBucketCollection Buckets;
 
  

However, I often find myself writing a narrative that includes rhetorical questions. In this case, I know what I’m doing, but my narrative comment is in the form of a question. I do this simply because it’s a more readable way of understanding what condition an “IF” statement tests:

 
  // Does this location have a tax authority?
 
  if (location.TaxAuthorityId > 0) {
 
  

Superfluous Comments

Beyond all these other types of comments lie the completely unnecessary. The purest form of unnecessary comments is the archetypal “add one” comment:

 
  // Add one to X
 
  X = X + 1;
 
  

It’s very hard to defend this kind of comment, but let’s stop for a moment to understand why it’s pointless. First, it’s documenting a task (addition) that is eminently readable; the comment adds no information beyond what is already in the code. Second, the comment does not increase the value of the narrative documentation in the code, since it adds no narrative to the variable.

If either of those two conditions change, the comment might be valuable. If someone was to obscure the increment operation by wrapping it in a subsidiary class whose method name wasn’t obvious, the comment might help you understand the function call without having to step into it.

 
  // This wrapper adds one to X
 
  X = TaskFactory.AdvanceWorkflowStage(X)
 
  

In another situation, perhaps the variable “X” is the thing that needs documenting. If we position this line of code in a location where adding one seems out of place or duplicative, we should ensure that our narrative explains why it was needed. For example, consider this hypothetical comment on a loop where we maintain two parallel counters:

 
  // Filter the database records we retrieved; when displaying, X is the actual rowcount
 
  int X = 0;
 
  for (int i = 0; i < a.length; i++) {
 
      if (!ShouldSkipRow(a[i])) {
 
          PrintRow(a[i]);
 
  
 
          // add one to the displayed rowcount
 
          X = X + 1;
 
      }
 
  }
 
  

But we should all acknowledge that the harm caused by an unnecessary comment is forgettable. If you don’t need to read the comment to understand the code, you can happily overlook it. On the other hand, if you get into the habit of imagining that all your code is obvious, you’ll stop writing comments and the codebase will become less readable.

Summary

We should all strive to write useful comments in a way that improves our programs. Yet, swear-word-filled-gem (not safe for work, in some workplaces): a series of comments so noxious the programmer who wrote it had to issue a full explanation.

How is your code commented?