Comments have been the target of much negative advice—delete them! rewrite to eliminate them!—but little positive advice. Let’s take a look at why they get a bad rap, then examine why the right kind of comments are indispensable.
// get the minimum X value of our bounds let x = view.bounds.minX
You’ve seen code like this. You might even cop to having written code like this.
Luckily, the problem is very localized: You need only look at a couple of lines to see that the author has duplicated what their code does into their comments. When the code changes, and the comments don’t—and they need not, since the compiler won’t look at them, anyway—the comment will make the code harder to understand. What started as background noise becomes harmful interference over time.
This problem has been with us since the beginning of programming languages. Kernighan and Plauger called it out in 1974 with rule 50 of their Elements of Programming Style: “Don’t just echo the code with comments—make every comment count.”
Over-commenting in this style can be very helpful when learning a new programming environment. Everything is unfamiliar, and the comments help you explain to yourself, in your own words, what your program is doing. They’re easier for you to read and follow than the still-strange code.
Once you get comfortable in that environment, the problem of over-commenting often goes away on its own, since the code is now plain enough for you to read without “interlinear translation.”
Once you get past basic API and syntax hurdles, the confusing parts of your code now become more abstract: Whole passages that seemed clear as day when you wrote them become clear as mud when you you return six months later. Over time, you might learn to leave yourself sign-post comments that minimize this problem.
But the same comment-code skew problem, where the code changes but not the comments, occurs here as well. Perhaps we can push what we want to communicate in comments down into the code itself, so that we are forced keep it up to date?
This leads to the pursuit of intention-revealing code. The hope is that, through careful selection of variable, class and method names, we can make our code so lucid that comments become unnecessary.
Intention-revealing code is a worthy aim. Its devotees’ zeal is not misguided.
But you see, it’s not really code per se that needs comments. We don’t have intention-revealing architecture; until we do, we must write documentation. READMEs aren’t just placeholders, they’re your chance to orient the next developer to the overall scheme of your program, what the major components are, where you got them from and how they all come together.
Comments should save time over the life of a program and reduce the amount of legwork the next developer needs to do in order to understand your program.
Valuable comments provide context that can be recovered only with significant effort or that could never be recovered absent the knowledge and intentions of the original author.
Sometimes our code is a myopic rendering of a higher-level idea. Which algorithm, from which book, is it implementing? Why was that algorithm selected, and which assumptions would lead to discarding it for one of its alternatives?
The dynamic, runtime context of our objects is often difficult to infer from the static source code. How does the app use this object, and when? What’s its typical lifecycle? What other objects does it collaborate with?
Sometimes, a comment is the only clue a developer will have, short of having been there from the beginning. Comments can record historical knowledge: Why is this class in this particular program? What about it is most likely to change? What’s proven a hotspot already?
Comments can also record intentional knowledge: Is there an easy way to extend the app to accommodate an expected change, that you’ve provided already, that might be overlooked in future?
You might notice that the method-level doc comments popularized by JavaDoc and available in Obj-C and Swift today are rarely the sort that provide this kind of value.
Unless you’re authoring a framework, you’re unlikely to derive much value from method-level comments. They’re little more than busywork. With the source code available to you, well-chosen names and liberal assertions, you’re unlikely to gain much from adding them to your own code.
Class-level comments, on the other hand, can provide significant value. They provide a chance to express, up-front, before you even begin writing the body of a class, what its responsibilities are. Documenting the other objects it will collaborate with to fulfill those responsibilities situates it within the context of the application. Class documentation focused on responsibilities and collaborators opposes responsibility creep by helping you keep your classes focused and comprehensible as your application grows.
But even exhaustive class-level comments will not begin to address many of the other kinds of context that we must rely on comments to communicate. Without such comments, the developer must resort to archaeology.
In light of software archaeology, don’t forget that this is the ultimate purpose of all those source control commits you’ve stacked up over the years. Small, distinct commits say you care, and good practice can make history significantly easier to navigate. I’d caution against leaning on version control exclusively for imparting this sort of information; I’ve been handed too many source-only tarballs over the years. Expect that your source code will eventually outlive its version history.
Comments form part of a project’s overall documentation. Unlike some documentation, comments’ intended audience is strictly other developers. A developer might be on the project for years, or for days; they might enter the codebase ten days from now, or ten years.
Consequently, comments are about providing two kinds of context:
Situating a local component (method, class, module…) in a wider non-local context: You’re new, so here’s how things work here, and come and meet the neighbors. Such comments amplify the efforts of someone new to the project by reducing the amount of the code base they must already be familiar with in order to be productive.
Situating the current application in a historical context: Here is where we started, here is why this is how it is, and here’s how we anticipated it changing in the future. These comments provide the knowledge someone needs to be a productive steward of the project in light of, and in spite of, its current, imperfect code.
If you comment with the whole picture in mind, you’ll reap the benefits thereafter. After all, a comment in time saves nine.
Interested in leveling up your coding skills from the same authors of the Big Nerd Ranch Guide? Subscribe to The Frontier today!