The War on Comments
January 11, 2012 đŹ Get My Weekly Newsletter ☞
Code comments often get a bad rap, especially in the Ruby community, with phrases like âcode should be self-documentingâ floating around, legitimizing the production of code with no documentation at all. In fact, code comments are one of the many useful tools developers have to make their intent clear, and writing them off as a âcode smellâ is naive and dangerous.
The Two Types of Comments
Before we dig in, itâs important to understand that there are two types of comments. There are comments that serve as API Documentation and comments that are, for lack of a better term, explanatory.
Iâm not going to argue that you should write API documentation for your public APIs. You should, and anyone insisting that you shouldnât is just being lazy. Itâs worth pointing out that API documentation comments are there to help you write code, primarily. They help answer the question âwhat code do I need to accomplish this task?â.
The other type of comment, explanatory comments, are there to help you read code, and this is why they are so important to use and to get right. Code is read many more times than written, and I doubt youâll find a professional developer who would argue against writing code for readability.
Explanatory Comments
Explanatory comments are those that seek to explain something about the code that the author (or maintiner) didnât feel was obvious from the code itself. Itâs at this point that many will say that the mere existence of such comments is a code smell, or indicator that the code should be rewritten in a more clear way.
This rule of thumb makes a lot of assumptions about what sort of comments one might write as well as the expressiveness of source code. Comments that merely explain what the code does are, in fact, a code smell. Comments that explain why the code was written a certain way, or even why it exists at all, are not. Understanding why code is the way it can be is incredibly hard to encode in a programming language. I still think âwhatâ comments have their place, but letâs tackle âwhyâ comments, as these are a powerful tool for code authors and maintainers.
âWhyâ Comments
Letâs take the example from my last blog entry: a salutation class. Suppose our system has the following implementation:
class Salutation
def initialize(person)
@person = person
end
def greeting
if @person.honorific == :doctor
"Hello, Dr. #{person.last_name}!"
elsif @person.first_name.present?
"Hello, #{person.first_name}!"
elsif @person.male?
"Hello, Mr. #{person.last_name}!"
else
"Hello, Ms. #{person.last_name}!"
end
end
end
This code is pretty clear, and itâs easy to understand what it does. A novice programmer might comment it like so:
class Salutation
def initialize(person)
@person = person
end
def greeting
# Check if they are a doctor, otherwise
# use their first name, falling back
# to their gender
if @person.honorific == :doctor
"Hello, Dr. #{person.last_name}!"
elsif @person.first_name.present?
"Hello, #{person.first_name}!"
elsif @person.male?
"Hello, Mr. #{person.last_name}!"
else
"Hello, Ms. #{person.last_name}!"
end
end
end
We can all agree these comments add noise. So whatâs missing from this picture that the code canât express? Something that seems odd is the explicit check for the honorific
of :doctor
. Why do we only check that one? Clearly there can be other values for honorific
; why donât we check those?
Code like this is all over the place in a real production application. This is part of what makes the job of a programmer challenging. We get requirements that are sometimes odd and result in akward code that isnât as clean as the idealized code youâd see in a book. And so we have the dreaded special case, which results in code that sticks out like a sore thumb.
Code like this, at first, looks like a bug: âOh no! What if someone registers with the honorific of âSirâ? Weâre going to call them by their first name?â. Suppose in this case, itâs not a bug. Suppose in this case that this is intentional. How would someone know that?
Explanatory Comments.
class Salutation
def initialize(person)
@person = person
end
def greeting
# We special case :doctor because the Initech
# style guide for messaging requires it, and we need
# to be compliant with it. Since we have very few users with
# honorifics other than doctor, we don't want to get into a
# complex honorific mapping mapping situation right now, so
# we just handle this one explcitly.
if @person.honorific == :doctor
"Hello, Dr. #{person.last_name}!"
elsif @person.first_name.present?
"Hello, #{person.first_name}!"
elsif @person.male?
"Hello, Mr. #{person.last_name}!"
else
"Hello, Ms. #{person.last_name}!"
end
end
end
Now it makes sense. We got some annoying requirement and had to meet it. The reality of the data in our system made the cost of a more generalized soluation have little benefit, so we did the simplest thing that could possibly work and were nice enough to explain ourselves.
This is the sort of comment that is incredibly useful for understanding code. I would argue that any time you do something weird or akward in your code, you should explain why you did it that way. Future you will thank you in the following ways:
- Youâll understand, six months from now, why you wrote what appears to be awkward code.
- The maintainer of this code wonât ask you about it.
Maintaining Comments
The second criticism typically leveled against comments is that they become out of sync with the code. Continuing our Salutation
example, suppose we later get a requirement to special-case college professors. The quick and dirty solution would be:
class Salutation
def initialize(person)
@person = person
end
def greeting
# We special case :doctor because the Initech
# style guide for messaging requires it, and we need
# to be compliant with it, and have very few users with
# other honorifics, so we don't want to get into a
# complex honorific mapping mapping situation right now
if @person.honorific == :doctor
"Hello, Dr. #{person.last_name}!"
elsif @person.honorific == :professor
"Hello, Prof. #{person.last_name}!"
elsif @person.first_name.present?
"Hello, #{person.first_name}!"
elsif @person.male?
"Hello, Mr. #{person.last_name}!"
else
"Hello, Ms. #{person.last_name}!"
end
end
end
Now the comment is out of sync with reality. We have a nice explanation of special-casing :doctor
, but not a word about :professor
. I would argue that the developer that wrote this code and called it âdoneâ has been negligent. Heâs not done his job and thereâs no excuse for this. At the very least, he should replace the phrase âWe special case :doctorâ in the comment with âWe special case :doctor and :professorâ. Thereâs many better ways to meet the new requirement, but for the purpose of keeping our focus on comments, you must keep the comments and the code in sync. Thatâs the job.
Hopefully, you can see that comments that explain why code is the way it is are important. These sorts of comments are a powerful tool for expressing intent and making code readable, understandable, and clear, beause the reality is, not every problem that we have to solve can be done so in perfect-looking âcleanâ code.
So what about those âwhatâ comments? What about comments that explain how code works and what itâs doing? Those are certainly wrong in every case, right? Not so. Hereâs a (qualified) defense of those sorts of comments.
Sometimes you need to explain what the code does
I donât write perfect code. My code isnât always written in the most elegant, understandable way. Yours isnât either. The reasons for this are many: we didnât have time to make it as clear as possible, we couldnât think of the right name for something at the time we write it, we didnât really undertand the problem as much as we thought we did, etc.
And, just because you omitted all comments doesnât make your code magically comprehensible. You might feel that code should be self-documenting, but leaving out comments doesnât make it so.
If you spent any time in real production code, you know that the most brilliant developers can write some real stinkers, creating convoluted code that can be really hard to unravel. Iâve done it many times, and Iâm certain my ex-co-workers at Opower and Gliffy curse my name from time-to-time. Itâs code like this that can benefit from some judiciously-placed âwhatâ comments.
Suppose we come across this code:
def isqrt(square)
square = square.to_i
return 0 if square == 0
raise RangeError if square < 0
n = iter(1, square)
n1 = iter(n, square)
n1, n = iter(n1, square), n1 until n1 >= n - 1
n1 = n1 - 1 until n1*n1 <= square
return n1
end
def iter(n, square)
(n + (square / n)) / 2
end
You might guess that this takes the square root, but boy is it impenetrable. Some things are just complex and either canât be made more clear or external constraints exist (like time) that we just canât do so. What we probably could do is add a few comments to help the poor sap that must fix a bug in this code later:
def isqrt(square)
# just in case we don't get an int
square = square.to_i
return 0 if square == 0
raise RangeError if square < 0
# make our initial guesses, which are initially
# too high (intentionally; see next)
n = iter(1, square)
n1 = iter(n, square)
# Refine our guesses until we get close enough
n1, n = iter(n1, square), n1 until n1 >= n - 1
# We're close enough when we're JUST under square
n1 = n1 - 1 until n1*n1 <= square
return n1
end
def iter(n, square)
(n + (square / n)) / 2
end
The code is more noisy, yes, but now the reader has some clue as to whatâs going on. It might even given him the confidence to refactor this in a way that no longer requires comments.
What about tests?
Tests can give insight into how code is supposed to work. Tests can rarely make clear the why of code, and almost never explain the complex guts of a routine. And, letâs be honest, even well-tested applications have horribly unreadable tests, rife with duplication, magic numbers, questionable coding practices and other horrors weâd never include in our production code. Weâll get to that.
The point is, comments are a way to include information that canât be (or wasnât) expressed in code right next to the relevant code. Thereâs power in that, and a develper that completely writes this power off is being foolish.
Bottom Line
- Document your public API
- Use comments to explain why odd or surprising code was written
- Use comments to explain what impenetrable code is doing if you just canât make it any more clear
Future you will thank you.