Commenting

In computer programming, a comment is a programmer-readable explanation or annotation in the source code of a computer program. They are added with the purpose of making the source code easier for humans to understand, and are generally ignored by compilers and interpreters. The syntax of comments in various programming languages varies considerably.1

There are several reasons for negative attitudes towards commenting.

1) Programmer's Hubris
2) Lazyness
3) No timeleft for documentation due to deadline constraints

None of these is a good reason for not commenting source code properly. Let's look at these arguments, discuss them and take a look at good commenting practice and its benefits.

1. The Case against Commenting

1.1 Programmer's Hubris

A good programmer is always a programmer with something of a well developed ego. Nothing is impossible, everything is easy to understand.

In practice, reality checks are in order from time to time. Do you understand all your code after not looking at it for, say, a year? Is legacy code left to you to maintain always obvious at first look, or even after a few weeks of scrutiny? Truth is, most of the time it will take a lot of time and effort to understand undocumented code, even if the code has not been obfuscated intentionally.

As an example, let us consider the solitaire encryption algorithm of Bruce Schneier in its 'concise' version as published in Neal Stephensons novel 'Cryptonomicon'. It is implemented in Perl without 'spaghetti code', yet due to its terse coding style, it is almost incomprehensible, should you attempt to figure out what it does:

#!/usr/bin/perl -s
## Ian Goldberg <ian@cypherpunks.ca>, 19980817
$f=$d?-1:1;4D=pack(C',33..86);$p=shift;
$p=~y/a-z/A-Z/;$U='$D=~s/.*)U$/U$1/;
$D=~s/U(.)/$1U/;';($V=$U)=~s/U/V/g;
$p=~s/[A_Z]/$k=ord($&)-64,&e/eg;$k=0;
while(<>){y/a-z/A-Z/;y/A-Z//dc;$o.=$_}$o='X'
while length($o)%5&&!$d;
$o=~s/./chr(($f*&e+ord($&)-13)%26+65/eg;
$o=~s/X*$//if $d;$o=~~s/.{5}/$& /g;
print”$o/n”;sub v{$v=ord(substr($D,$_[0]))-32;
$v>53?53:$v}
sub w{$D=~s/(.{$_[0]})(.*)(.)/$2$1$3/}
sub e{eval”$U$V$V”;$D=~s/(.*)([UV].*[UV])(.*]/$3$2$1/;
&w(&v(53));$k?(&w($k)):($c=&v(&v(0)),$c>52?&e:$c)}

Imagine you confront code written like this, with your employer expecting you to maintain it. Now facing such a daunting task, the avenues that might lead to understanding this terse piece of code are:

1) Stare at the code for a long time and have a headache.

2) Single step through the program a few times with varying input, to see what it does.

3) Look at the plain text explanation of the algorithm, if available.

The best alternative however is to have properly commented code. The very same algorithm including comments (and a few lines to make it a bit more user and maintainerfriendly) is perfectly easy to understand and adapt to individual requirements:

#!/usr/bin/perl -s

## Solitaire cryptosystem: verbose version
## Ian Goldberg <ian@cypherpunks.ca>, 19980817

## Make sure we have at least the key phrase argument
die "Usage: $0 [-d] 'key phrase' [infile ...]\n" unless $#ARGV >= 0;

## Set the multiplication factor to -1 if "-d" was specified as an option
## (decrypt), or to 1 if not. This factor will be multiplied by the output
## of the keystream generator and added to the input (this has the effect
## of doing addition for encryption, and subtraction for decryption).
$f = $d ? -1 : 1;

## Set up the deck in sorted order. chr(33) == '!' represents A of clubs,
## chr(34) == '"' represents 2 of clubs, and so on in order until
## chr(84) == 'T' represents K of spades. chr(85) == 'U' is joker A and
## chr(86) == 'V' is joker B.
$D = pack('C*',33..86);

## Load the key phrase, and turn it all into uppercase
$p = shift; $p =~ y/a-z/A-Z/;

## For each letter in the key phrase, run the key setup routine (which
## is the same as the keystream routine, except that $k is set to the
## value of each successive letter in the key phrase).
$p =~ s/[A-Z]/$k=ord($&)-64,&e/eg;

## Stop setting up the key and switch to encrypting/decrypting mode.
$k = 0;

## Collect all of the alphabetic characters (in uppercase) from the input
## files (or stdin if none specified) into the variable $o
while(<>) {
 ## Change all lowercase to uppercase
 y/a-z/A-Z/;
 ## Remove any non-letters
 y/A-Z//dc;
 ## Append the input to $o
 $o .= $_;
}

## If we're encrypting, append X to the input until it's a multiple of 5 chars
if (!$d) {
 $o.='X' while length($o)%5;
}

## This next line does the crypto:
## For each character in the input ($&), which is between 'A' and 'Z',
## find its ASCII value (ord($&)), which is in the range 65..90,
## subtract 13 (ord($&)-13), to get the range 52..77,
## add (or subtract if decrypting) the next keystream byte (the outputof
## the function &e) and take the result mod 26 ((ord($&)-13+$f*&e)%26),
## to get the range 0..25,
## add 65 to get back the range 65..90, and determine the character with
## that ASCII value (chr((ord($&)-13+$f*&e)%26+65)), which is between
## 'A' and 'Z'. Replace the original character with this new one.
$o =~ s/./chr((ord($&)-13+$f*&e)%26+65)/eg;

## If we're decrypting, remove trailing X's from the newly found plaintext
$o =~ s/X*$// if $d;

## Put a space after each group of 5 characters and print the result
$o =~ s/.{5}/$& /g;
print "$o\n";

## The main program ends here. The following are subroutines.

## The following subroutine gives the value of the nth card in the deck.
## n is passed in as an argument to this routine ($_[0]). The A of clubs
## has value 1, ..., the K of spades has value 52, both jokers have value 53.
## The top card is the 0th card, the bottom card is the 53rd card.
sub v {
   ## The value of most cards is just the ASCII value minus 32.
   ## substr($D,$_[0]) is a string beginning with the nth card in the deck
      $v=ord(substr($D,$_[0]))-32;
   ## Special case: both jokers (53 and 54, normally) have value 53,
   ## so return 53 if the value is greater than 53, and the value otherwise.
      $v>53?53:$v;
}

## The following subroutine generates the next value in the keystream.
sub e {
## If the U (joker A) is at the bottom of the deck, move it to the
top
 $D =~ s/(.*)U$/U$1/;
 ## Swap the U (joker A) with the card below it
 $D =~ s/U(.)/$1U/;

 ## Do the same as above, but with the V (joker B), and do it twice.
 $D =~ s/(.*)V$/V$1/; $D =~ s/V(.)/$1V/;
 $D =~ s/(.*)V$/V$1/; $D =~ s/V(.)/$1V/;

 ## Do the triple cut: swap the pieces before the first joker, and
 ## after the second joker.
 $D =~ s/(.*)([UV].*[UV])(.*)/$3$2$1/;

 ## Do the count cut: find the value of the bottom card in the deck
 $c=&v(53);

 ## Switch that many cards from the top of the deck with all but
 ## the last card.
 $D =~ s/(.{$c})(.*)(.)/$2$1$3/;

 ## If we're doing key setup, do another count cut here, with the
 ## count value being the letter value of the key character (A=1, B=2,
 ## etc.; this value will already have been stored in $k). After the
 ## second count cut, return, so that we don't happen to do the loop
 ## at the bottom.
 if ($k) {
               $D =~ s/(.{$k})(.*)(.)/$2$1$3/;
           return;
 }

 ## Find the value of the nth card in the deck, where n is the value
 ## of the top card (be careful about off-by-one errors here)
 $c=&v(&v(0));

 ## If this wasn't a joker, return its value. If it was a joker,
 ## just start again at the top of this subroutine.
 $c>52?&e:$c;

It admittedly is longer than the terse version of the code, but it is more readable by far. Even if you are not familiar with the Perl programming language, you will by now have some idea of what the program does. This is what commenting is about. We also can see from this example that there are various functions comments may have in the scope of a program. Of course, comments may be stripped out of the final production code if there are severe restraints on storage space. On modern machines, this usually is no longer necessary though – perhaps when doing client side scripting or writing code for fiendishly small embedded thingies. And even if you do strip out comments, always keep a commented version of the sources at hand for future work.

1.2 Lazyness

That is no excuse at all. The idea behind it is a fallacy in the best of cases. All time saved by not commenting during the coding process is made up more than twice at least by inserting comments and providing documentation after the coding process is finished. In a real production environment, documentation of the code written is a requirement. As there nowadays are a number of systems available for the automatic generation of documentation out of comments, you will actually save yourself a lot of hours of boring documentation work by simply inserting appropriately formatted comments as you code.

1.3 The Deadline Problem

So, you're not writing comments because of problems with the project deadline? Then something is very, very wrong with the whole approach taken for managing that project. Omitting commenting will not at all help. Sorry. The few minutes saved this way every day will only make the crunch harder as the deadline comes closer. Then you will need to get things right fast. And that cannot be done by having undocumented code that is hard to figure out when bug hunting or refactoring yet another time is at hand. Actually we save time by commenting properly. Bad project management is enough of a problem to cope with. We do not need unreadable, undocumented source code as well.

2. Commenting Properly

There are several purposes that comments may serve:

  • Documentary comments

  • Functional comments

  • Explanatory comments

2.1 Documentary Comments

This class of comment serves to document the history and development of the source file or project in question. Into this category belong the following comments that should be included in every source file:

  1. Filename
  2. Version number/build number
  3. Creation date
  4. Last modification date
  5. Author's name
  6. Copyright notice
  7. Purpose of the program
  8. Change history
  9. Dependencies
  10. Special hardware requirements (e.g. A/D converters)

This type of comment obviously is intended to document the software itself, its purpose and its history. The intent of these is to make life easier for maintenace of code that is expected to have a life expectancy going beyond an 'ad hoc' script's. Any code planned to be in use for more than a few weeks should absolutely contain these comments.

PCMBOAT5.PAS*************************************************************

File:              PCMBOAT5.PAS

Author:            B. Spuida

Date:              1.5.1999

Revision: 1.1      PCM-DAS08 and -16S/12 are supported.
                   Sorting routine inserted.
                   Set-files are read in and card as well as
                   amplification factor are parsed.

1.1.1              Standard deviation is calculated.

1.1.2              Median is output. Modal value is output.

1.1.4              Sign in Set-file is evaluated.
                   Individual values are no longer output.
                   (For tests with raw data use PCMRAW.EXE)

To do:             outliers routine to be revised.
                   Statistics routines need reworking.
                   Existing Datafile is backed up.

Purpose:           Used for measurement of profiles using the
                   Water-SP-probes using the amplifier and
                   the PCM-DAS08-card, values are acquired
                   with n = 3000. Measurements are taken in 1
                   second intervals. The values are sorted using
                   Quicksort and are stacked "raw" as well as after
                   dismissing the highest and lowest 200 values as
                  'outliers'.

Requirements:      The Card must have an A/D-converter.
                   Amplifier and probes must be connected.
                   Analog Signal must be present.
                   CB.CFG must be in the directory specified by the
                   env-Variable "CBDIREC" or in present directory.

*************************************************************************

These comments may be inserted manually, as was the case for this program, but as this manual insertion and updating process is error prone, it is recommended to use the placeholder strings made available by versioning tools. Using versioning tools is 'Best Practice' for mid to large scale projects, so some form of placeholder for the most vital points (1 – 5,8) usually is at your disposition.

A tip: whenever you rename a file, immediately change the appropriate comment. Otherwise, you are certain to forget it and automated documentation or building systems may run out of kilter.

2.1 Functional Comments

Functional comments serve one purpose: adding functionality to the development process. They do not describe the code or its development history but serve single processes in the development cycle. The most obvious functional comment type is a 'to do' comment. There are others, though:

  • Bug description

  • Notes to co-developers

  • Performance/feature improvement possibilities

Such comments should be used sparingly, even though they are important. Together with the
documentary comments, they will provide a history documenting the design of the program. They always should be in the same format, so they may be easily extracted or searched for. It is good practice to have uniform 'TODO:' comments etc., so that portions of code that need reworking or need improvement are found reliably.

'I will always insert these comments where necessary, immediately when I find they are necessary, and always use the standard form agreed on by the entire development team.'

Into this category, we might also count comments documenting bug fixes. These should give who fixed what bug when and how. Of course, we might also consider these comments as documentary comments. This is however, merely a 'philosophical' question to consider. Let us just always insert these comments.

2.1 Explanatory Comments

This type of comment is quite obvious in its function. Well written code will contain a lot of these, even though explanatory comments are obviously not necessary for each single line of code. Items that should have explanatory comments are:

  • Startup code
  • Exit code
  • Sub routines and functions
  • Long or complicated loops
  • Weird logic
  • Regular expressions

Commenting startup code is good practice, as this will give an idea of what arguments are expected and how they are handled, how the program is initialised, what the default values and settings options for the configuration variables are, what #defines do etc.

The same goes for the exit code, both for normal and abnormal exit situations. Return values, error codes etc. should be properly explained.

Each subroutine or function should be commented, stating the purpose of it. Also the arguments passed and returned should be explained, giving format, limits on values expected etc.

The limits on values are an especially important point to be documented in comments. Quite a number of bugs and even completely broken applications result from not stating clearly whether there are limits on value ranges for input and expected return values. What will happen if we try to stuff a 1024 char string into an 128 char buffer? Guess who will have to take the blame for the consequences if this happens 'merely because we forgot to document' that particular limit?

As for commenting 'weird logic', this is vital to the future maintainability of the code. Regular expressions for example often tend to be obscure, so explaining what they look for is recommended. If you want to be very clear about what they do, you can use the option 'x' (or set the 'IgnoreWhiteSpace' RegexOption in C# alternatively), to split the regex up over several lines, adding in comments where needed.

void DumpHrefs(String inputString) //We're extracting href tags in this
{
   Regex r;
   Match m;

   r = new Regex("href\\s*=\\s*(?:\"(?<1>[^\"]*)\"|(?<1>\\S+))",
   RegexOptions.IgnoreWhiteSpace|RegexOptions.IgnoreCase|RegexOptio
   ns.Compiled);

   for (m = r.Match(inputString); m.Success; m = m.NextMatch())
   {
      Console.WriteLine("Found href " + m.Groups[1] + " at "
      + m.Groups[1].Index);
   }

}

void DumpHrefsclean(String inputString) //same as above, commented
{
   Regex r;
   Match m;
   r = new Regex("href #This looks for the string 'href'
   \\s*=\\s #followed by whitespaces, '=', ws
   (?:\"(?<1>[^\"]*)\" #a ':', + a group in '"', no '"' in it
   | #or
   (?<1>\\S+))", #a group followed by non-spaces
   RegexOptions.IgnoreWhiteSpace|RegexOptions.IgnoreCase|
   RegexOptions.Compiled);

   for (m = r.Match(inputString); m.Success; m = m.NextMatch())
   {
      Console.WriteLine("Found href " + m.Groups[1] + " at "
      + m.Groups[1].Index);
   }

}

Note that in this C# code segment the comments inside the regex have to be Perl-style, using '#' as delimiter. The comments extend from the '#' to the next newline character.

Another case of 'weird logic' would be switching between different programming paradigms – sequential, object oriented and functional or stack oriented programming may serve as examples. Such switches can be quite confusing when they are done without telling the reader. The following Perl code from T. Zlatanov's book illustrates this point for a filtering process for odd numbers in procedural and functional implementation:

my @list = (1, 2, 3, 'hi');
my @results;
# the procedural approach

foreach (@list)
{
  push @results, $_
  unless $_%2;
}

# using grep – FP approach
@results = grep { !($_%2) } @list;

2.1 General Commenting Style Recommendations

As we have seen in the above sections discussing types of comment, a useful comment always follows some basic rules of style. And as nowadays the API documentation for most programs is generated automatically from comments using one of the systems described in section 4 below, coherent, clearly written comments make for good documentation. Users will assume inferior product quality when they are confronted with bad documentation.

Question number one many programmers6 tend to ask is 'how much comment is good for my code?', or in other words, 'with how little comments can I get away?'. This is not such an easy question to answer, as a simple numeric relationship between #linesofcode/#linesofcomment cannot be naively applied. Obviously, in a short program the share of comments will increase in comparison to a large scale software project's comment share. Personal experience shows though, that there seems to be a limit ratio approached in well documented code:

The ratio of code/comment lines tends to be about 2/1 in sizeable projects.
This ratio includes all three types of comment. Therefore, this empirically derived ratio is not a recommendation for verbose 'blow by blow' commenting style.

Now this takes us immediately to the next point: verbosity. There is no need to comment the obvious such as:

a++ // increases the counter
return} // returns from the subroutine

Such comments are a) an insult to your reader's intelligence and b) tedious to write. So just don't do it. Comments should be long enough to make their statement, but still concise. Detailed explanations can be worked into the documentation later on wherever necessary, after documentation has been generated from the sources. As a rule of thumb, the explanation of one line should not be longer than a line. Also, there usually is no need to explain a two line loop using a full paragraph of comment.

Once you settle for a naming scheme, stick with it. No exceptions to the rule!

Practical experience has shown that despite some disadvantages the socalled 'Hungarian Notation' used in Win32 programming is very useful. See the article by Hopfrog given in the references below for an overview of this style.

Another Golden Rule to be learned comes from Ottinger's Rules for Variable and Class Naming:

Nothing is intuitive

This holds true for naming as well as for code clarity. Read Ottinger's paper regardless of your preferred programming language and naming conventions. It contains many useful insights. Once again: comment your code!

Comments also can be kept short and concise by using one line commenting style wherever
possible. Multiline comments should be used preferably for the comment block at the program file's head or for subroutine explanation comments.

The layout of the comments should be kept consistent wherever possible. Start comments always in the same columns if it can be done, e.g. at either column 1 or column 40. Consistent layout makes comments more easily searchable and will result in better looking documentation when the comments are extracted for this purpose.

For functional comments, the spelling of the keywords must be consistent, to help in finding the 'hot spots'. For example once you have settled for writing 'TODO', never use 'To do', 'todo' or 'To Do'. A todo comment usually should be as close as possible to the location in the source code where it should be done.

In explanatory comments, especially when dealing with the description of subroutines or with workarounds around bugs, it is always recommended to state the problem as well as the solution, as this will a) make your code easier to understand and b) therefore easier to maintain and refactor and c) it will make you think more clearly about what you are doing and thus lead to your writing better code right from the start.

C# Xml Comments

For the .NET framework, and the C# language specifically, another documenting system has been designed in which XML code is embedded into standard C# comments. These tags can then be extracted using either the C# compiler with the /doc: option set or third party tools such as NDOC. These tools will convert the comments extracted into some other format, e.g. Windows Help files.

The nice part of this xml-based system is that the fields most often needed in professionally commenting code are predefined xml tags:

alt

JavaScript Comments

/**
* This is a description
* @namespace My.Namespace
* @method myMethodName
* @param {String} str - some string
* @param {Object} obj - some object
* @param {requestCallback} callback - The callback that handles the response.
* @return {bool} some bool
*/
    function SampleFunction (str, obj, callback) {
         var isTrue = callback(str, obj); // do some process and returns true/false.
         return isTrue ;

References