Does your code have comments? Sometimes they’re helpful. But most of the time…
Disclosure: The book links below are affiliate links. If you buy anything, I earn a commission, at no extra cost to you.
As Jeff Atwood explains, code tells you how, but comments tell you why. A well-placed code comment is a level above the code itself, explaining why something is written the way it is. But! We can express most comments as code, using well-named identifiers. The first edition of the Refactoring book called this Introduce Explaining Variable. Martin Fowler has since renamed that refactoring to Extract Variable.
Code Comments: Example from Exercise
Let’s look at a specific example from my Code Smells exercise:
override func viewDidLoad() {
super.viewDidLoad()
if viewModel.isAlternateStyle {
// Alternate style needs different placeholder text.
textField1.placeholder = strings.field1Hint
textField2.placeholder = strings.field2Hint
setUpAlternateView()
} else {
textField1.placeholder = strings.field1Placeholder
textField2.placeholder = strings.field2Placeholder
setUpNormalView()
}
}
First, I apologize for the awkward names. Many people said the generic names like textField1 and textField2 were a code smell. Normally, yes! But they’re so generic because I want to keep my job: I anonymized a section of real code I had encountered at work.
Mark Bernstein responded to the code smells exercise with many good observations, including:
The first smell is the comment; if part of the code needs explanation, it can be explained by making it a method.
In other words: extract and name. This might be as a variable or as a method, depending on how many lines we’re talking about. But give it a descriptive name.
Moving Things Around
How many lines are we talking about for this comment? It says,
// Alternate style needs different placeholder text.
Clearly, this applies to the next two lines, which set the placeholder property for textField1 and textField2. But wait, they’re also set in the else clause. And the comment is talking about the difference.
What if we separated that if-else block into two such blocks? Like this:
override func viewDidLoad() {
super.viewDidLoad()
if viewModel.isAlternateStyle { // 👈
// Alternate style needs different placeholder text.
textField1.placeholder = strings.field1Hint
textField2.placeholder = strings.field2Hint
} else {
textField1.placeholder = strings.field1Placeholder
textField2.placeholder = strings.field2Placeholder
}
if viewModel.isAlternateStyle { // 👈
setUpAlternateView()
} else {
setUpNormalView()
}
}
This unifies the things that are the same (setting the placeholders for the text fields). It separates the things that are different (setting up the rest of the view to match the desired style).
“But now we’re repeating the if statement!” Sometimes, to clean things up, you first need to make it messier. If you’ve ever organized a messy closet, you know what this is like.
Now the comment could move up so it comes before the first if statement:
// Alternate style needs different placeholder text.
if viewModel.isAlternateStyle {
textField1.placeholder = strings.field1Hint
textField2.placeholder = strings.field2Hint
} else {
textField1.placeholder = strings.field1Placeholder
textField2.placeholder = strings.field2Placeholder
}
The code comment now lives in the right place: it attaches itself to the entire if-else block. Try to make the scope of your comments obvious.
But at this point, it’s pretty clear that the comment isn’t adding any value. It’s just describing the code.
When you see a code comment that only describes the code, delete the comment.
Rules of Thumb for Code Comments and Refactoring
Here are some rules of thumb for code comments, with methodical refactoring steps you can take.
Let’s summarize what we’ve seen in this example. First, about code comments:
- Make the scope of your comment clear. You can do this for a single line by attaching the comment to the end of the line. For multiple lines, use blank space to separate the commented part from the rest of the code.
- Use your comment to explain why the code is necessary. Don’t explain the how; that’s what the code does.
- If a comment only repeats the code, delete it. ’Nuff said.
- Can you transform the comment into an identifier? Use Extract Variable or Extract Function. Then you’ll have self-documenting code!
Finally, some observations about refactoring:
- If your IDE provides automated refactoring, use it. (Xcode is getting better on this front, but still refuses to extract certain things. AppCode is the winner for automated refactoring.)
- Bring similar things closer together. Move dissimilar things further apart.
- Don’t be afraid to make a slight mess while you clean, even if it means increasing duplication. Some things are hard to see until you move them around.
But! Before we do any refactoring, let me slam the brakes. We’re not ready to make any changes to the original code! There’s one big, big principle about refactoring we need to look: do you have tests?
Search your codebase for comments. Do any have any ambiguous scope? Can you change any into code? Can you simply delete any? Let us know how you did in the comments below…
All Articles in this series
Great article. Here is another “why” comment I use: the URL of an OpenRadar or StackOverflow answer explaining the necessity or appropriateness of some hack.
Ooh good one, Josh
I’ve done this before, too, and I like it a lot. Or sometimes when it seems like the code is doing something it shouldn’t, I’ll link to the ticket that describes the bug or requirement.
The link to the Jeff Atwood article is broken — looks like it somehow got doubled. I think it should be: https://blog.codinghorror.com/code-tells-you-how-comments-tell-you-why/
Whoops! Fixed. Thanks for pointing that out, Josh.
I’d add another rule: remove any commented out code that you come across. It provides no value and can be incredibly confusing for someone coming to the codebase later on.
Thanks for the article. I’d refactor viewDidLoad as (keeping same level of abstraction by removing those if to viewModel and setUpView function) :
I like your post. I would have tried a different way to clean this up. I would have moved the placeholder assignment into functions like so:
Then, I would have considered whether the placeholder assignment was part of the setup. If it was, I would have moved it into the setup like so:
Thanks for thinking it through, Russ! I cover my own approach in related articles, with detailed steps on how I did it. But it wasn’t clear that there was a series, and why this post focused only on the comment smell. So I just added a big block at the bottom of each post to make it more obvious. Check them out when you have time.