Commenting Code

Bad Advice

I’ve encountered a lot of bad advice about commenting code over the decades.  I’ve known developers who insisted that commented code is actually worse than uncommented code because the comments will always be wrong or out-of-date.  But even dated comments give a clue about the original intentions of the code and how subsequent changes might have gone wrong.

I’ve seen style guides that recommend a one line comment above every paragraph of code. Mindlessly adding short comments every few lines ends up with obvious statements like “add one to index” that don’t actually provide any useful information.  Back in the 70s or 80s when hand coding assembly language was still a thing, such comments were bread and butter of the profession.  But modern high level languages are usually self-documenting in that regard.

Self-Documenting Code

Self-documenting code has all the sound and feeling of moral superiority.  But, like Communism’s “from each according to their ability, to each according to their need” which sounds all noble and just, in the real world it just doesn’t work.  This is often for the same reason: some people are just evil, and many more are just plain lazy.

But it also doesn’t work because programming languages are not intrinsically expressive enough to self-document more complex ideas.  More often than developers realize (because developers are too close to the problem and make assumptions about what a later developer will intuitively understand), the problem being solved is complex, with much jargon, hidden assumptions, and irreducibly complex algorithms being necessary to solve it.

A further issue is the fact that well-done comments serve more purposes than explaining what a bit of code does.  So leaving the political analogy snark above behind, I introduce another analogy to explain the many purposes of comments.

The Book Analogy

Let’s consider the text of a book as an analogy to the text of a program.  A well written book can be read from cover to cover without the aid of typographical conventions and be understood.  Consider the popularity of books-on-tape as proof of this idea.  The text of the book is effectively self-documenting.

But now let’s look at a couple of pages of a real book.  (Wallace, Daniel B. Greek Grammar Beyond the Basics.  Zondervan, 1996.)

We see many features that are not part of the text itself, but which aid the reader in navigating and understanding the text:

  • Titles
  • Headings
  • Subheadings…
  • Overviews
  • Bibliography
  • Footnotes
  • etc.

All these have useful counterparts in commenting code to make the easier to navigate and understand.

Titles, Headings, and Subheadings…

A hierarchy of titles and headings make navigating a book (and code) easier.  In a book they are usually set off by size, and often weight or font selection as well.  We don’t have such typographical conventions available in development environments.  Instead a construct called “block comments” have traditionally been used instead.

/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
////                                                         ////
//// MyPojo                                                  ////
////                                                         ////
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////

public class MyPojo {

    /////////////////////////////////////////////////////////////
    //                                                         //
    // CONSTRUCTORS                                            //
    //                                                         //
    /////////////////////////////////////////////////////////////

    public MyPojo() {
        …
    }

    public MyPojo(MyPojo source) {
        …
    }

    /////////////////////////////////////////////////////////////
    //                                                         //
    // PROPERTIES                                              //
    //                                                         //
    /////////////////////////////////////////////////////////////

    /////////////////////////////////////////////////////////////
    // primaryKey                                              //
    /////////////////////////////////////////////////////////////

    private String primaryKey;

    public String getPrimaryKey() {
        return primaryKey;
    }

    private String setPrimaryKey(String primaryKey) {
        this.primaryKey = primaryKey;
    }

    ///////////////////////////////////////////////////////////
    // widgetName                                            //
    ///////////////////////////////////////////////////////////

    private String widgetName;

    …

    ///////////////////////////////////////////////////////////
    //                                                       //
    // BUILDER                                               //
    //                                                       //
    ///////////////////////////////////////////////////////////

    public class Builder {

        ///////////////////////////////////////////////////////
        // Constructors                                      //
        ///////////////////////////////////////////////////////

        MyPojo template;

        public Builder() {
            …
        }

        …

        //////////////////////////////////////////////////////
        // Initializers                                     //
        //////////////////////////////////////////////////////

        public Builder primaryKey(String primaryKey) {
            template.setPrimaryKey(primaryKey);
            return this;
        }

        public Builder widgetName(String widgetName) {
            …
        }
    }
}

Headings in code can be given different font sizes and weights virtually by using heavier or lighter boxes around the heading text and by using all capitals, mixed case, or all lowercase text.

Creating headers this way has several advantages:

Style guides recommend grouping related constructs together, such as constructors, properties, inner classes, etc.  By exposing the structure imposed by these guidelines in such a hard-to-miss fashion, future developers are less likely to place constructs in the wrong place.  Experience shows that without such headings, groupings of constructs inevitably break down as the code is further processed.

The typographical conventions are what they are because several centuries worth of experience has discovered that these conventions cooperate with the way the brain works.  By being able to pick out the higher level structure of the text (or code) prior to actually reading it, the mind is prepared to more quickly learn what the text has to say (or what the code does).  Headings speed up the learning curve.

Readily visible visual markers make it easier for even the readers who are most intimately familiar with the text (or code) to quickly navigate from place to place.  The visual shape of the different headings becomes a map in the reader’s brain.  They are the mile posts that the reader will remember  where specific passages are located relative to.

Overviews

Many developers like to gather the declarations of backing-store variables ahead of all the getters and setters to serve as a makeshift Overview section.  This is problematic for three reasons.  First, breaking up of the implementation of a property into two places makes the code harder to follow or modify.  But more importantly (and second), it frequently doesn’t serve as an adequate summary because (with some regularity) there is not always a one-to-one correspondence between backing-store variables and properties.  Finally, third, there is frequently more than just properties that can benefit from an Overview.

My preference generally is to add an overview to the title box for the class, giving a quick list of constructors, properties, inner classes, methods, etc.

/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
////                                                         ////
//// MyPojo                                                  ////
////                                                         ////
//// Constructors:                                           ////
////     MyPojo()                                            ////
////     MyPojo(MyPojo source)                               ////
//// Properties:                                             ////
////     primaryKey                                          ////
////     widgetName                                          ////
//// Inner Classes:                                          ////
////     Builder                                             ////
////                                                         ////
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////

An alternative would be to put the overview in a Javadoc comment on the class.  But generally IDEs already provide this information in name completion and the tags needed within the Javadoc for semantic annotation and formatting are hardly human readable.  The Overview is for the human reader of the source code to get a high level view of what the class contains, not for the IDE to read and generate separate documentation.

Bibliography

Any well written code most probably has design documents that may or may not be part of the source code itself.  That is, they may be held in a repository of Word documents such as One Drive or in a Wiki.  Where documentation is not held in the source file itself, the source file should contain citations of those documents.

Code that is more scientific in nature will often be based on published algorithms.  Citations to these documents should also be included in the source to aid future developers.

There are numerous styles of bibliographic citations.  Which one to use is not particular important.  But it is important for intranet or internet citations to include more than just a URL as URLs have an unfortunate tendency to become stale.  When citing a URL, include the date that the URL was valid.  This will aid in finding it in an internet archive site.  But also include standard bibliographic information such as author, title, publisher, and date of publication like would be done for any paper resource as these will aid in finding the resource again after the URL has become stale.

When citing a particular location within a document, don’t just cite the page number, as a future developer may be accessing an online version of the document.  Cite the section heading and possibly multiple levels of section headings, as this will help the future developer find the intended location in documents stripped of page number information.

Citations that cover the whole source file can be placed in the title box along with the overview.  Otherwise that may be attached in regular comments.  See “Footnotes” below.

Footnotes

The way that comments are most often thought of, as a short remark explaining what a small piece of code does, most closely correspond to footnotes in book typography.  There are, however, many functions that may be served by these footnotes.

Javadoc comments (or the equivalent in other program languages and development environments) serve as documentation for individual classes, methods, properties, and variables.  They often have semantic or formatting annotations within for generating printed documentation or documentation within integrated development environments, and so aren’t necessarily extremely human readable.

Just like the Bibliography section above, it is often useful to provide cross references to other documents, or even other code within the project, on smaller pieces of code.  This is analogous to what is probably the most common form a footnote in books: citing sources.

Summarizing what a paragraph of code does is often useful.  But caution must be exercised to not go overboard.  Writing a comment that says “Add one to index” is not very useful, whereas “collect sums needed for calculation of average and standard deviation” probably is.

More often than not, the summarizing comment will actually provide a rationale for the code.  For example, where a comment saying “Add two to index” isn’t useful, a comment that says “Process array elements in even-odd pairs” on an increment by two probably is useful because it explains why we are incrementing by two.

Self-Documenting Code Redux

I’ve seen style guides that champion self-documenting code that would argue that the right way to document the “even-odd pairs” processing in the above example would be to refactor the code to have a processEvenOddPair() method.  This can certainly be a useful technique, but pushed to extremes tends to fragment the code into a non-linear mishmash of methods.  And extremes are what some style guides recommend.  I’ve seen one that insists no method should be more than 5 or 7 lines long!

Such code is nearly unreadable and very difficult to debug.  I know, because I’ve tried to read and debug such code before.  The human brain tends to process input in a linear fashion.  When one has to interrupt the brain to go looking for the next method, the mental stack gets overwhelmed.  Trying to trace through seven levels of function calls, each with long parameter lists typically, is just as hard as trying to follow seven levels of deeply nested loops spanning multiple pages of code.

A few judicious comments on well-paragraphed code result in much easier to read and understand code.  This is not to say that the concept of self-documenting code is useless and should be abandoned.  By all means, methods and variables should be given precise, accurate, self-documenting names.  And overly large blocks of code should be divided up into smaller methods, especially when an obvious, intuitive, object-oriented abstraction can be made, or when a complex piece of code has too many levels of deeply nested loops.

Paragraphing

While strictly not a comment in the explicit syntactical sense, grouping lines of code together into paragraphs of related code (with paragraphs separated by a blank line) is an important way to make code self-documenting.  When the lines of code that do a “thing” are scattered, the human brain has make considerable effort to work out how the “thing” is done.  It also makes future refactoring easier when an intuitive abstraction becomes obvious.

Afterthoughts

Writing good comments and keeping them up-to-date is hard.  Often even harder than writing good code in the first place.  When up against a deadline, it becomes tempting to crank out the code without concern for comments.  This almost always results in inferior code because the developer is not forced to impose structure and thoughtfulness on the code, and it becomes technical debt that slows down future enhancements and bug fixes.

While crunches are unavoidable, they should always include scheduling time after the crunch is over to clean up the code and comments.  Developers should consider it a matter of pride in their work to create code that is both stable and maintainable long after they are gone.

An analogy (I do like analogies don’t I?) would be a building contractor who puts up temporary walls and pillars while inserting new beams in a structure.  The contractor will always come back and replace the temporary structures with finely finished structures.  The programmer in a crunch is doing the equivalent of temporary walls.  They shouldn’t still be the primary wall ten years later as all too often happens in the software world.