Comments are good, actually

7 minutes, 2021-04-29. Back to main page

After that 99 Bottles of OOP post, I’ve been thinking a lot about coding style. In particular, there’s a perspective I hear sometimes that code should be self-documenting.

There’s a strawman version of perspective that I want to get out of the way first — I don’t think anyone’s seriously proposing that we not use comments anywhere. But I do hear people talk about how comments can easily become out-of-date and eventually no longer reflect what’s actually happening in the code. They say you should try and pack as much information into variable and function names as possible. “Comments should be rare; we should break our code up into a lot of small functions with descriptive names so that the code itself tells us what it’s doing.”

The trouble with that is, naming functions and variables is hard. Besides cache invalidation, it’s the only hard problem in computer science, or so the aphorism goes. So advocates of self-documenting code will say, yeah, obviously it does take a while to come up with good names. There’s no way around it.

But let’s think for a minute about what makes it so hard to name things. Computer science is a complicated and abstract field. Much like philosophy, it deals mostly in ideas that are meticulously structured and rather unlike anything in the physical world around us. When you name a function, you need to take a complex and abstract process and compress it into a short alphanumeric slug — maybe 25 characters long at the most. That’s barely any space, and we have a lot of information to convey.

So consider that the statement “your code should be self-documenting” can be reworded as, “your code should explain what it does mostly within the confines of function and variable names.” That’s actually a pretty tough sell, given how poor a medium names really are. We can spend our time much more efficiently by writing comments than we can by cramming every relevant detail into names. Good names are important, of course, but let’s not expect the unreasonable from them.

And the stale comments? Far worse than stale comments are stale names. People may sometimes forget to update a comment, but they will rarely change a function name to make it more precise. Appending a sentence to a comment takes a couple seconds and barely any effort, but renaming a function is refactoring. You’re gonna need your IDE to do that for you. What if that method is specified in an interface? How many method names do you have to change now?

The barrier for changing names is much higher, so it’s that much easier to just let them go stale. Granted, stale comments are a problem, but I don’t know if the solution is to avoid using them. I think the solution is to be more proactive about comment maintenance. Take advantage of the low barrier for change and update comments fearlessly.

Annotating code with function names also risks making the code less clear. In order to annotate a paragraph of code using a function name, you actually need to refactor the code out into a new function. Not only does that create a barrier to adding annotations, it actually obscures the code you wanted to clarify. A comment adds to a code block, but a function name replaces it. You want to see what’s actually happening there, you need to go look somewhere else. This is sometimes desirable, but sometimes it just obscures what the code is actually doing.

Once a block of code has been extracted into its own function, changing that block of code can become harder. If someone re-used that function elsewhere, making changes to it suddenly has much wider consequences. And if that winds up having been a bad place to break up the code, you can fix it by inlining the definition, but again, that’s refactoring. The barrier for change is made much higher.

These are small details, but they add up. A codebase which has been meticulously parted out into countless tiny functions can be hard to read and hard to change. Is this really a price worth paying, especially when your only reward is the privilege of using function names instead of comments?

A couple things got me thinking about this subject. The first is that I went back to some code I’d written a few months ago. When I wrote it, I thought that the function names were clear enough that it would be obvious what was going on. Coming back to it with fresh eyes, I realized that what would have really made things obvious is more comments.

It’s not that the code wasn’t legible — I managed fine. But I would have been able to understand precisely what the code was doing and why more quickly if there were comments that described the situation in full sentences, instead of relying on terse names. It costs very little to write a brief comment above a block of code, but it can wind up being pretty helpful for someone skimming that code later on. Maybe it only saves you a few seconds, but that adds up — especially for a newcomer to the codebase.

The other thing that got me thinking was a line from 99 Bottles of OOP which said that blank lines are a code smell, and that they are a sign that you should refactor that code into a series of smaller functions. Having thought about it for a while now, I’m prepared to disagree completely. If you find yourself breaking code into paragraphs like that, and you think it needs clarifying, don’t jump straight to refactoring it out into several smaller functions. Maybe it is worth breaking it up, but maybe you could just add a comment before each paragraph that explains what’s going on there and why. Depending on the context, I think you’ll find that it’s more legible and has a longer shelf life than the alternative.

Obviously, there’s a point where it makes sense to split code into multiple functions. If it doesn’t fit on your screen all at once, definitely think about breaking it up. But that’s more about legibility than it is about being “self-documenting,” and splitting code between too many functions can prove even more illegible. Sometimes a block of code does deserve to be its own function, but that’s an explicit choice to hide the details of what that code does behind an abstraction, not just a way of explaining what that code does. As far as explanations go, I think a comment is better at explaining what a piece of code does than a name is anyway.

I feel like the logical endpoint of this discussion has to be literate programming. Literate programming is a pretty rare practice these days. I’ve never actually seen it in the wild; I only know it by reputation. But some very good programmers swear by it. Maybe they’re onto something? I don’t know if it’s worth completely inverting things and writing code inside of comments instead of the other way around, as literate programming suggests, but I’m really warming up to the idea that the clearest code is code that isn’t afraid to explain itself in plain English.