Guidelines for commenting code


Writing good comments is hard and we developers have come up with several excuses to not write comments at all. In this post I will try to debunk some of the most common excuses for not writing comments and give some good reasons for writing them and how to write them so that they add as much value as possible.

I want to start with the most important thing about comments; remember this: comments should describe things that are not obvious from the code. Let’s look at an example of a completely useless comment:

// Returns the persons age
int Person::getAge() const;

Comments like the one above are the main reason developers think that comments are useless. It does not add any value at all. If the majority of comments you have seen (or added yourself) are like this one, it is no wonder you think comments doesn’t provide any value.

Another argument for not adding comments is the belief that good code is self-documenting. Unfortunately this is not true. There are some things you can do to make the code easier to understand, like choosing good function- and variable names, but there is significant amount of design information that simply cannot be represented in code. You want your comments to capture information that was in the mind of the developer but couldn’t be represented in the code.

What to comment on

There are a few categories of comments that should be the most common. These are:

  • Interface comments which precedes a declaration of a class, data structure, function, or method.
  • Comments on data structure members, such as an instance variable for a class.
  • Implementation comments which are nested in the code and describes what is happening and why.

Of these categories the first two are the most important. Every class and every method should have an interface comment and every class variable should have a comment.

Writing good interface comments

A class or a method represents an abstraction. Unfortunately code isn’t suitable for describing abstractions; it’s too low level and include implementation details that should not be visible in the abstraction. Interface comments should provide the information that someone needs in order to use a class or method. They should not describe how the class or method works internally. Let’s look at an example how we can improve the description of a phone book class:

/**
* This class implements a phone book using std::unordered_map for quick look-ups.
* Several maps are used and kept in sync. It manages the maps internally, the user
* just includes "PhoneBook.h" in a header to use the PhoneBook class.
* Mutexes are used to secure only one thread at the time can alter the contents of the maps.
*
* To use PhoneBook, the user creates an object of this class by calling it's constructor with
* the necessary information. New entries are added by calling add(), look-ups are done
* by calling lookUp(), and entries are removed by calling remove(). If an entry is not
* found lookUp() returns std::nullopt.
*/
class PhoneBook {
  ...
};

Can you identify any problems with this comment? One problem is that the first paragraph concerns the implementation and not the interface. The second paragraph includes obvious information, such as that the constructor is used to create an instance. It also describes the different methods, which might be better to put next to them instead of in the description of the class.

Here is an example of how the comment can be improved:

/**
* This class implements a simple phone book.
* Insertions, removals, and look-ups are all normally performed in constant time (worst case linear).
* All operations are thread safe which makes the class suitable for multi threaded use.
*/
class PhoneBook {
  ...
};

This is a short and simple description that provides information that focuses on the things that may be of importance to the user of the class but leaves out unnecessary implementation details.

Continuing to the methods of the phone book, each public method should have a descriptive comment that explains what it does and any behavior that isn’t obvious, for example what happens if the caller tries to add an entry for a person that already exists.

/**
* Add an entry to the phone book.
* Casing of first- and last names will be maintained, however it is not possible to add
* several entries where only the casing is different. If an entry with the same combination of first- and
* last name already exist it is overwritten. White space characters at the front and at the end are trimmed.
* 
* Phone number must include country code, area code, and number; separated by dashes. For example "+46-8-1234567".
* A std::invalid_argument exception is thrown if a number is provided that doesn't match the expected format.
*/
void PhoneBook::add(std::string firstname, std::string lastname, std::string phonenumber);

It would, of course, be possible for a developer to dive into the code and figure all these things out, but it would take a lot longer time and be more error prone. The developer would also need to read a lot of implementation details that are not necessary to understand in order to call this function.

Good implementation comments

Implementation comments are not always needed. For small functions that does one thing they can often be left out completely. Sometimes however, you have long functions with different blocks of code that performs different steps. In those cases it is a good idea to add a comment that describes what the next block of code does. Remember, the comment should not describe how the code does what it does, that information is better understood from reading the actual code.

In his book Clean Code Robert Martin advocates for breaking out code into many small methods instead of adding comments. However, in my experience this approach often results in methods with long, cryptic, names that provides less information than a well written comment. Only break out code into separate methods if they can stand on their own. Ask yourself if it would make sense to call the method from somewhere else; small private methods that are only called from a single place can be merged into the calling code and a well written comment can be added.

If the code contains a large loop it can be helpful to add a comment just above the for– or while statement that describes what happens in each iteration. Small loops don’t need these comments.

Conclusion

Comments are necessary to describe things that aren’t obvious from the code. When adding comments, try to put yourself in the mindset of a developer who sees this code for the first time. What are the key things he or she needs to know about this code?

Also, when you find yourself scanning through code and trying to make sense of it. Ask yourself what information that isn’t obvious from the code and add that as a comment. A simple way to figure out if a comment is missing is if you need to go into the body of a method or function in order to figure out how to use it.

,

Lämna ett svar

Din e-postadress kommer inte publiceras.