In defense of (some) code comments

Code comments are often seen as bad practices, but some of them can be used to encourage continuous and incremental refactorings.

Applying engineering principles in code — just like everything we do — depends on a context. Good software engineers leave their personal preferences aside to make important decisions. We analyze the context and we refute absolute truths. We are critical enough to accept that there are things that will remain in articles and books we read because they don’t make sense to be applied in our day-to-day.

It’s the same logic for things that we normally see as bad practices and code smells: we shouldn’t judge them upfront.

“It’s easy to critique code in retrospect. That’s fine as long as we remember that we don’t know the original context in which the code was developed. Code is often written under strong pressures of time constraints and changing requirements.” — Software Design X-Rays.

Besides not judging, sometimes we can use depreciated techniques to our advantage. Code comments, for example, can be handy in facilitating continuous and incremental refactorings.

Refactorings

Keeping the codebase healthy is the only way to go fast and to deliver value frequently. Continuous refactoring is the way to keep the codebase in shape because we're always improving the code and thus making it easier to add new functionality. On the other hand, an "ill" codebase holds us back and can get to the extreme point of sacrifice: call it legacy and start a new one from the beginning.

Refactorings can be trivial and simple but also huge and complex. In the first case, it's easy to identify the problem and tackle it. But in the second, we get that "I don't know where to start" feeling: the problem is so deep-seated and big that it is difficult to think of a feasible solution.

To fight that feeling and make refactoring a habit we have to accept there is no perfect moment to start a complex refactoring. Stop postponing it and just do it: split it into much smaller pieces, get the first one to work on, and ship them as you go.

“Its essence is applying a series of small behavior-preserving transformations, each of which “too small to be worth doing”. However the cumulative effect of each of these transformations is quite significant.” — Martin Fowler.

There’s no need to create technical tasks for them because they are part of the user stories. Having refactoring tasks means we are not doing them frequently enough.

Another common obstacle in incremental refactorings is the ease of losing focus: we begin with something in mind, then new ideas start to pop-up, and we realize we've piled up a bunch of different refactorings at the same time. Refactorings are like git commits: they should be small, cohesive and green (i.e. all tests passing).

There are specific types of code comments that can help us refactoring on the right track. Let's see them.

Normally, code comments are a sign that the code is not descriptive enough and, in general, they should be avoided. But in this specific case, they’re useful to point out incremental refactorings that are still in progress and to keep us focused while refactoring. Those are the ones I use on my daily basis.

WIP — Work In Progress comment

Technical debts can be huge but we have to start “paying back” somehow. WIP comments are used when we give the first step of the refactoring but we know the next ones won’t be done subsequently. This is not a problem if the first step already gives us benefits: remember the boy-scout rule motto “always leave the code behind in a better state than you found it”. In a better state does not mean prettier, though.

“(…) don’t be surprised if some of the steps you take to make changes involve making some code slightly uglier. This work is like surgery. We have to make incisions, and we have to move through the guts and suspend some aesthetic judgment.” — Working Effectively with Legacy Code.

WIP comments make sense when dealing with big problems and non-trivial solutions. One of these days we tackled a design problem by splitting a God class using the splinter pattern: we extracted functions that were domain-related to another class.

We added a WIP comment to flag that the refactoring is not finished yet, i.e. we haven’t achieved the final idea we had in mind. In case we introduced a different/unusual pattern, the comment will also guide the reader to understand why it was done like this (for example:TransfersHub is the only dependency that is not being injected).

In the hypothetical next step, we would remove TransfersHub from the God class entirely and delete the comment. But this requires more effort and we won't tackle it at the moment.

FIXME / DONEXT comment

This one helps us to keep focused while refactoring. Following the previous example, the goal was the extraction to TransfersHub. But imagine we had another idea along the way: renaming the class to TransfersService, for example. Instead of doing it straight away, we could add a //FIXME or //DONEXT to the class so we can do it in the next commit.

// DONEXT: Rename to TransfersService
class TransfersHub {}

This technique works pretty well with the practice of doing small and atomic commits. It might seem ridiculous to do a separate commit in the example above, but commits should do only one thing that can be described in a short phrase. That way, we can easily rollback a refactoring without affecting other ones.

Ideally, FIXME / DONEXT comments shouldn’t be committed because they are just reminders that shouldn't last long. I even created a git pre-commit hook to avoid committing them:

To ship the code we’ll have to open the file, remove the comment, and BAAAAM: you’ve got the next refactoring right in front of you.

TODO comment

The two types of comments shown above can replace the need of using TODO comments. Instead of marking it as TODO, why don't we do something about it already, even if it's a humble start, and mark it with a // WIP? Or if it's something trivial, we could do it right after and mark it as a // DO NEXT .

The problem with TODO comments is that they are easily forgotten and they don't provide us immediate benefits. We pass the ball but we don't know if anyone else is going to catch it. And the chances are that nobody will, and the comment will remain there.

Wrapping up

I used to judge code comments myself, but WIP and DONEXT comments help us to make refactorings less painful by splitting them, encouraging us to refactor more frequently.

It’s always better to refactor right away than to add a comment and leave it for later. But let's be real: sometimes we can’t start and finish it in a reasonable way. This doesn’t mean we should be waiting for the perfect moment, we can start now. Then we can use:

  • WIP comments: to make it clear we had started a refactoring that is not finished and inform the reader it might found different patterns in the code.
  • FIXME/DONEXT comments: to avoid losing focus and accumulating refactorings and also in keeping the changes really small.

Both of them force us to split problems into smaller pieces and they work really well together. Leave your judgments aside and try them!

➕ IDE configuration

Most IDEs have a tool that helps to visualize and track TODO comments. We can customize it and add the keywords we want, and it starts recognizing them as "special" comments. Here's how they look inside a class:

In this view it inspects the codebase in different scopes to keep track of how many special comments are there:

By using this IDE tool, we don't even need the git pre-commit hook I mentioned before, because it will automatically check for special comments before committing through the IDE:

Here's the configuration I'm using in case you want to explore and benefit from it:

Product Engineer. Constantly delivering tested code. Using user’s shoes. Improving everyday.