How comments in programs are used these days

Alonso Del Arte
13 min readSep 18, 2018
Theoretically you can use dingbats and emoji in source code. Wouldn’t recommend it, though.

With very few exceptions, every computer programming language has a syntax for comments, which are remarks the compiler or interpreter ignores. These are meant to elucidate the program for other people reading the source code.

I get the sense that comments in computer programs these days are often not used in the ways that the designers of the languages intended. That’s not always a bad thing, though.

I’ve heard people saying that good code is “self-documenting.” There is some validity to that. I take it to mean that variables and functions are given meaningful names, and excessively cute or clever techniques that obscure algorithms are avoided.

For example, consider this fictional example:

        meter.calibrate(FACTORY_DEFAULTS);

If you see something like this in a program by someone else that you’re reviewing, it should be fairly easy to understand what this is supposed to do.

Hopefully meter is an instance of a class with a meaningful name, like VoltageMeter or Speedometer. Hopefully that class has a good Javadoc for calibrate(), for its constructor and for the class as a whole.

And hopefully FACTORY_DEFAULTS also has a good Javadoc. If these expectations are met, then any sort of comment on the line quoted above would quite likely be entirely superfluous.

I’ve also heard that tests document the source. Which is valid only as long as whoever needs to look at the source code in the future also has access to the test packages.

The common wisdom still repeated today accords comments a certain measure of permanence. There is this idea that comments are something you or your pair will be happy to see a few months down the line, when you have half-forgotten how your program does what it is supposed to do.

While holding this view, most people in practice actually regard comments as something transient which will be removed before finalizing the source code for the next release.

With of course the exception of documentation comments meant for a documentation generation tool like Javadoc.

I’ve even heard there are plugins that automatically remove comments, though I imagine they can be configured to leave documentation comments if that’s not the default setting.

My unscientific sampling of open source projects shows plenty of documentation comments but single-line comments can be somewhat of a rarity even within a single project.

Take for example Twitter’s Algebird, a Scala project. The constructor of MethodRegistry.java contains five single-line comments, two of which have a definite “this will be helpful to me in the future” flavor:

// because of type erasure scala primitive types just become Object                                 if (operandType != Object.class                                     && !conflictingDefinitions.contains(operandType)) {
if (classToMethod.put(operandType, method) != null) {
classToMethod.remove(operandType);
// let's not create ambiguity
conflictingDefinitions.add(operandType);
}
}

It was perhaps because of the interaction between Java and Scala that programmer Julien Le Dem thought it necessary to provide these clarifying comments.

Actually, as I look more around, it seems that Le Dem in general does write such comments as a matter of habit, and more so than other projects I’ve looked at.

Programmers use comments for various purposes these days, but explaining what a particular line or group of lines does to a future code reviewer seems to hardly ever be the purpose, at least by my unscientific sampling.

Despite the variety of purposes for comments, I think inventors of new programming languages will continue to provide comments in the same two ways the old inventors did: single-line and potentially multi-line.

Let’s now look at the various ways in which programmers do actually use comments for on a regular basis.

Temporarily deactivating troublesome lines

Both NetBeans and IntelliJ have a keyboard shortcut to “comment out” lines in the program. I’m sure Eclipse also has a shortcut for that, and it’s probably the same key combination.

It’s actually a toggle. But somehow I doubt that anyone is ever deliberately writing actual comments without first marking them as such and then using the keyboard shortcut to put them in the comment delimiters.

Continuing with the meter scenario, this next example shows a commented out line followed by a comment explaining why the preceding line is commented out:

    meter.calibrate(FACTORY_DEFAULTS);
// meter.adjustVariance(2 * MINIMUM_TOLERANCE);
// Problem with adjustVariance, makes all our tests fail
meter.load(this.preferences);

Eventually lines deactivated that way will either be toggled back to active after some adjustment, or deleted altogether.

In the minds of some programmers, “comments” have become synonymous with “lines commented out.”

This helps convince me that comments are actually regarded as ephemeral by people who spout the common wisdom about the future code reviewer.

To do (reminders)

The perfect use of a temporary comment is to remind yourself of something that needs to be done but which, for whatever reason, you don’t have time to do today.

IntelliJ presents most comments in gray, except for comments beginning with TODO, which it then presents in light blue (I think this is also true of TO DO). That way you can quickly scan for such comments.

Even better, IntelliJ gathers up all the To Do comments in a project and can present them in a single, convenient window.

Here is an example of comments reminding us that we haven’t written a check for division by zero in these division functions:

    public Fraction dividedBy(Fraction divisor) {
// TODO: Check divisor is not zero, throw exception if it is
return this.times(divisor.reciprocal());
}
public Fraction dividedBy(int divisor) {
// TODO: Check divisor is not zero, throw exception if it is
return new Fraction(this.numerator, this.denominator * divisor);
}

Hopefully this will pass all the relevant tests but fail the test that checks that division by zero causes a specific exception to be thrown. Unless maybe the Fraction constructor checks for division by zero, or in some other way triggers the expected exception.

It is almost needless to say that these comments are not meant to make it to the version of the source code the next stable release will be compiled from.

Planning the program (pseudocode)

We can take the concept of To Do comments to its logical extreme and use them to write our plan for a function or procedure, or even an entire program, before we write even one line of syntactically valid code.

Of course an integrated development environment (IDE) like NetBeans or IntelliJ helpfully guides us to certain syntactic minimums.

For example, in planning to write an implementation of the Legendre symbol (a number theoretic function relating prime numbers and squares), we might have the function parameter declaration and the return of a perfunctory value suitable for getting a failing first test.

Sandwiched in between those, we’d have our pseudocode as comments:

    public static byte symbolLegendre(int a, int p) {
// Check that p is prime
// Compute (p - 1)/2
// Power mod: a^((p - 1)/2) mod p
// If power mod is p - 1, return -1
// If power mod is p, return 0
// If power mod is 1, return 1
return -2; // This is just for the failing first test
}

Of course the last comment will be the first to go, right after we get a test of symbolLegendre() to fail for the first time and right before we start fleshing out how symbolLegendre() actually accomplishes the computation.

Planning comments should only be deleted once you’re sure the subroutine does what you intend it to do, or at least you have a failing first test.

Comments about flaws in reasoning

You might write something that passes all the tests and works well enough but still have the sense that something is not as efficient or as elegant as it could be.

Even worse, there could be a flaw in your reasoning and an inelegant workaround to compensate for it, but because computers are so fast these days, you might not notice any penalty hit.

The most you can do when you have this awareness of a flaw in your logic but can’t put your finger on it is to write a comment where you think the problem is at.

Everyone makes mistakes, and I’ve sure made my share of boneheaded, amateurish mistakes. The following is from an actual commit of the test suite of a program of mine, edited to avoid excessive indentation and to focus attention on the pertinent comment.

  for (int i = 0; i < totalTestIntegers; i++) {
try {
result = testIntegers.get(i).times(testConjugates.get(i));
} catch (RuntimeException re) {
result = zero; // Avoid variable may not be initialized error
fail("RuntimeException should not have occurred");
}
assertEquals(testNorms.get(i), result);
}

Catching a generic exception is not a good practice, but since the specific exception is not relevant to the discussion at hand, I’ve left it out of the example.

If the multiplication in the try block triggers an exception, result might not be initialized when the program gets to the assertion after the catch block. And that is the only reason to set result to complex zero.

But the fail() in the catch block will stop execution of the test subroutine before reaching the assertion, so the value of result is irrelevant if an exception causes the test to fail.

Therefore the assertion on result should be in the try block, and then I can delete result = zero without getting any compilation errors.

This all seems perfectly clear and obvious to me in hindsight, but now I can’t say for sure I would have realized it without writing the comment.

  for (int i = 0; i < totalTestIntegers; i++) {
try {
result = testIntegers.get(i).times(testConjugates.get(i));
assertEquals(testNorms.get(i), result);
} catch (RuntimeException re) {
fail("RuntimeException should not have occurred");
}
}

Actually, I had also made this same mistake in the tests of plus() and minus(). I didn’t correct those until earlier today, instead of taking care of it last month when I corrected the times() test.

In my divides() test, the first initialization of result in a catch block points up a deeper problem: that my test of divides() depends on times() working correctly.

It’s not a difficult problem to solve, it’s just I’m not going to have time to fix it today. So for now that one improper initialization stays, the others have been corrected.

Once a mistake is understood and corrected, there is no further need for the comment about it. Do be sure to check the comment has indeed been deleted.

Comments as indicators of insufficient granularity

Some people say that no function or procedure in a program should be more than four lines long. That is a bit draconian, in my opinion.

However, I think that if a subroutine is so long that it requires scrolling in the editor to look at it all, you should at least consider the possibility that maybe it needs to be broken up into smaller pieces.

Some programmers will sometimes insert blank lines in the middle of a subroutine to indicate a change of focus. I sometimes find myself also adding a comment to explain what that change of focus is. Or sometimes instead of the blank line.

Of course comments like that could also be the result of planning comments like the ones described earlier. Here is a fictionalized example based on an actual program.

It is fictional mostly in the sense that this was not my actual thought process. This is from RingWindowDisplay in my Java program to draw diagrams of primes in the complex plane.

    @Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// If enough pixels per unit interval, draw grids
if (this.pixelsPerUnitInterval > THRESHOLD) {
// If applicable, draw grid for "half" integers
if (this.ring.hasHalfIntegers) {
int verticalDistance;
g.setColor(this.halfGridColor)
...about 30 lines omitted...
}
// Now the grid for "full" integers
g.setColor(this.fullGridColor)
...another 30 lines omitted...
}
// And now to draw the points
int currPointX, currPointY;
g.setColor(this.primeColor)
...about 250 lines omitted...
}

These comments function somewhat like chapter headings that could be used to generate a table of contents. Chapter 1 is drawing the grids. Chapter 1 section 1 is drawing the grids for “half” integers. Chapter 1 section 2 is drawing the grids for… you get the idea.

These “headings” should suggest at least one way this long procedure can be broken down into shorter procedures.

The shorter procedures all depend on the Graphics instance g, but that can just be passed around. If we need to add declarations for local variables, the IDE (NetBeans or IntelliJ or other) will alert us with bright red error flags.

Here is paintComponent() after shipping most of its work to other procedures:

    @Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// If enough pixels, draw grids
if (this.gridsOn) {
// If applicable draw "half" grids
if (this.ring.hasHalfIntegers()) {
// Draw "half" grids
this.drawHalfGrids(g);
}
// Draw "full" grids
this.drawFullGrids(g);
}
// Draw points
this.drawPoints(g);
}

Admittedly drawHalfGrids(), drawFullGrids() and drawPoints() are still fairly long procedures by the 4-lines guideline. I don’t think there would be any performance penalty from passing g around some more.

Whether those shorter procedures would gain anything in clarity from being further broken down is debatable. But if the decision is made to break them down as well, the process would be much the same.

As the paintComponent() example shows, once sufficient granularity has been achieved, the comments that earlier indicated insufficient granularity now feel glaringly redundant and can now be safely deleted.

License and copyright comments

Perhaps for as long as programming languages have had commenting syntax, programmers have put information about authorship and copyright in comments at the beginning of the program.

Although comments with license and copyright information are technically documentation, they typically don’t use the special comment syntax that the documentation tool expects.

At least not the way NetBeans generates them. By default on Windows, NetBeans uses your operating system user account name, so you might have to change that to your full name in some NetBeans preference or change it on every single file.

/*
* Copyright (C) 2018 Alonso del Arte
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
...a few lines omitted...
*/

I know IntelliJ can also generate comments like this, but I don’t know how to set it up.

Under this heading also fall comments about confidentiality, work product and such. The following example is fictional because I’m bound, at least by the honor system, to not quote publicly the actual ones that I’ve seen.

/*
* Copyright (C) 2018 Acme Corporation
*
* All information contained in this source code is technical,
* proprietary, trade secrets. Any employee who willfully discloses
* any of this information is subject to the penalties outlined in
* his/her non-disclosure agreement, including but not limited to
* termination from employment by Acme Corporation.
*/

Documentation comments

I don’t know what programmers and/or technical writers used to document APIs before documentation generation tools existed. But I can easily imagine the sorts of nightmare scenarios that could ensue from a failure to update the documentation in a timely manner.

By having the documentation intermingled with the source code, it is much easier to update the documentation when needed. You don’t have to open a separate file, and even though that’s not a problem for a modern IDE, it might still be easy to forget to update the documentation.

Though the IDE helps a lot with keeping the documentation up-to-date (such as by alerting you that you need to update the @param tags in the Javadoc when you change the parameter list for a function), one should still proactively review the documentation soon after making changes.

I don’t know if Javadoc was the first documentation generation tool ever, but it is certainly the best known and the most imitated. C# uses /// repeated on several lines, enclosing XML documentation tags.

As far as the Java compiler is concerned, Java only has two distinct comment styles (to end-of-line and potentially multi-line), and they both result in the enclosed text being ignored by the compiler.

But the Javadoc tool distinguishes between multi-line comments that start with /* (a slash and just one asterisk) and those that start with /** (a slash and two asterisks).

Every public constant, public variable, public function and public procedure in the program should have a documentation comment (although public variables are frowned upon in Java, and also somewhat in Scala).

Protected and private variables, functions and procedures can also have documentation comments if those are helpful to the programming team.

NetBeans by default will show you documentation upon hovering on a relevant identifier, though I prefer to keep the Javadoc pane open at all times, grouped with the output and test results panes.

To show documentation for a given identifier, IntelliJ’s default key map on Windows requires the counter-intuitive keyboard shortcut Ctrl-Q, which many if not all Adobe programs running on Windows use as a quit program keyboard shortcut.

Of course in Mac OS X, Command-Q will quit IntelliJ; the documentation keyboard shortcut on that operating system is Control-J (not Command-J, which does something else).

If you do write documentation comments for protected and private items, be sure to limit Javadoc to public when generating documentation for use by the general public.

Depending on your workflow, it might be best to put off writing Javadoc for a particular item until all the relevant tests pass.

Please don’t take this to mean that test suites shouldn’t have documentation comments. I think that documentation comments relieve some of the pressure of having to come up with the perfect, most descriptive test names.

Here’s an example for the classic divide by zero scenario:

    /**
* A test of dividedBy method, of class Fraction. This makes
* sure that division by zero causes either
* IllegalArgumentException or ArithmeticException. Any other
* exception, or no exception at all, causes the test to fail.
*/
@Test(expectedExceptions = {IllegalArgumentException.class,
ArithmeticException.class})
public void testDivisionByZero_causesException() {
Fraction frac = new Fraction(1, 2);
frac.dividedBy(0);
}

Note that this requires TestNG (I don’t know if you have to delete the extra whitespace in the annotation). It can be done in JUnit but it’s a little more verbose than I wanted for this example.

Comments in tutorials and exercises

One reason comments may be necessary in tutorials is that code in a tutorial might be missing the sort of context that would appear in actual production code.

Comments can also help explain the less intuitive features of a programming language or API. Though I think that in most programming languages it would probably be asinine to comment an assignment statement if the whole purpose is just to show how assignments are done.

And of course comments in exercises are permanent in the sense that while one student may delete them, they will hopefully be restored for the next student who does the exercise.

In summary

With meaningful identifier names and thoughtful documentation comments, the need for permanent clarifying comments in a program is reduced if not altogether eliminated.

Temporary comments still serve many important purposes, like helping planning with inline pseudocode, indicating tasks that need to be done or problems that need to be fixed, and temporarily deactivating problematic lines.

Documentation comments should be written at some point for all public items in a program, and perhaps also for certain protected and private items.

--

--

Alonso Del Arte

is a Java and Scala developer from Detroit, Michigan. AWS Cloud Practitioner Foundational certified