Beyond “Clean Code”: Why Your Comments Matter

By
Posted on
Tags: , , , , , , , , , ,

I have argued that comments are necessary in good code. My rationale, in short, is that your code’s logic can only express one thing clearly and faithfully: what you actually instructed the computing device to do. But your code’s audience—the people who will read and maintain your code—must also understand what you intended the device to do and the all-important “why” information behind your coding decisions.

To the extent that your programming language lets you express these things naturally in your logic, you should do so. But don’t contort your logic to accommodate intent and “why” information that would be easier for your audience to understand in natural language. Just use a comment! That’s what comments are for! To let you speak directly to your audience in plain language.

But not everyone agrees. In his book Clean Code, Robert C. Martin (aka Uncle Bob) writes that “comments are always failures”:

“The proper use of comments is to compensate for our failure to express ourselves in code. Note that I used the word failure. I meant it. Comments are always failures. We must have them because we cannot always figure out how to express ourselves without them, but their use is not a cause for celebration. So when you find yourself in a position where you need to write a comment, think it through and see whether there isn’t some way to turn the tables and express yourself in code.”

(Note: what Mr. Martin calls code I call logic to distinguish it from commentary.)

I have a hard time swallowing Mr. Martin’s argument because it goes too far. If he had claimed that comments must be used with care, lest they become a crutch to prop up logic that ought to be rewritten instead, I would have heartily agreed. But “comments are always failures”? That admonishment is harmful. Why? Because eschewing comments makes code harder to understand and maintain by forcing you to express in logic any ideas that would be more clearly and directly communicated via commentary.

As I have argued above, logic cannot be trusted to express intent: it represents only what the device was actually told to do. If your logic contains an error, was that your intent?

Further, the syntax of logic does not allow for natural language, only a crude and limited approximation of it, and natural language is the, well, natural way to express the all-important “why” information underlying your logic. That’s why the original source materials for this information are almost always written in natural language. Consider:

In production code, this kind of information is often the most important. How would you communicate it clearly without using comments? Should you name your function ScoreAGameOfBowlingAccordingToTheUnitedStatesBowlingCongressRulesAtBowlDotComSlashKeepingHyphenScore? To communicate that an array-length invariant holds for the second half of your algorithm’s logic, should you exile that half to a new function you invented solely to let you name its argument array_having_length_3n_plus_1_for_some_integer_n? Does breaking the natural flow of your algorithms to invent naming sites make your code clean? Does using unwieldy names instead of natural language make your code clean?

Or contorted?

To be clear, I am emphatically not arguing against expressing intent and “why” information in logic. I believe that you should express them in your code’s logic to the extent you reasonably can without contorting your logic.

If you ignore the names in your logic and just look at its structure, is that structure a simple, natural expression of your solution and the problem domain? If it is anything more than that, that additional complexity is probably contorting your logic. Why did you contort your logic? Was it to create naming sites where you could insert words as a crude, limited approximation of natural language? Maybe don’t do that. Maybe just use a comment instead.

Everything in your code, both logic and commentary, should communicate your intent as clearly as it reasonably allows. So please do structure your logic in a way that’s natural to your problem domain and please do choose informative (but not unwieldy) names. But if there’s more to communicate—and there usually ought to be—don’t contort your logic to shoehorn it in. Accept that logic, as a medium for communicating intent and “why” information, is limited. Don’t try to make it do things it’s not good at. Just use a comment instead.

Discussion

Let me address some of the common objections to using comments.

What about the idea that “comments can lie”? If I never use comments, what my code says it does and what it actually does will always be the same, right? Isn’t that better?

No, it’s worse. Consider the possibilities when a comment disagrees with its associated logic: (1) the comment is wrong, (2) the logic is wrong, or (3) both are wrong. In case (1), the code at least still does what it should, but in cases (2) and (3) the code will do the wrong thing—oops! launched the missiles—and that’s a problem in the real world. The good news is that you at least know there is a problem and can investigate, thanks to the red flag raised by the comment and logic disagreeing, a red flag that AI agents are pretty good at noticing. Now imagine the same code but without the comments. Now there’s no red flag to draw your attention while coding or during code reviews. In case (1), there’s no real-world impact, but in cases (2) and (3) your code will happily continue to do the wrong thing while you remain unaware.

Don’t comments go stale? Programmers sometimes forget to update their comments when changing their logic, don’t they? Isn’t it better, then, to avoid comments and eliminate this problem?

No, it’s worse (see the answer above). Further, let’s think this idea through. As I hope I have argued convincingly, comments are essential: they are the most effective means of communicating all-important “why” information to your code’s audience. So when an organization doesn’t value comments enough to keep them up to date, what does this neglect say about its engineering culture? If you’re concerned about comments going stale, improve your engineering culture (for example, through code reviews). But don’t eliminate comments! That will only reinforce a broken culture and make it harder for your audience to understand and maintain your code.

Aren’t comments a crutch to prop up bad code? Aren’t they a “code smell”?

It is true that if you have confusing logic, you can make it more understandable by adding comments. And, for this reason, confusing logic is often propped up by supporting comments. But that just shows that comments are effective: they’re good at conveying useful information to your audience. If you add good comments to bad code, the bad code gets better. Likewise, if you add good comments to good code, the good code gets better. So write good code and good comments.

But don’t believe for a second that you’ve made your code better if you have contorted its logic to eliminate helpful comments. Use your good sense:

A challenge if you disagree

If you think I’m mistaken about the value of comments or the limitations of logic as a medium for expressing intent and “why” information, then consider this short Python library that implements discrete random variables. Kindly show me how to eliminate its internal comments without discarding useful information or contorting its logic.

If you don’t like Python but want to try the challenge, just let me know in a comment or email, and I will suggest some code in the language of your choice.