The Engineer’s Guide to Writing Meaningful Code Comments
“What on earth does this do?”
“Why wasn’t this a ticket?”
Been there? If you’re anything like most engineers, it’s safe to say this happens to you a lot.
The most obvious and immediate benefit of writing good comments is that they make code easier for others to understand.
But when we nail our code commenting best practices (which include when to and when not to write comments), we can unlock even more impactful, long-lasting benefits like:
- improved engineering velocity
- better code quality
- managing and reducing technical debt
On the other hand, when we don’t embrace good practices, code comments can have the opposite effect. After all, code comments are an unstructured way of storing information. They’re not easily searchable or visible beyond looking at the code. This makes them a poor alternative for proper documentation or issue tracking. Combined with the code quality nosedive and velocity issues they cause, poor commenting leads to mounting problems, from huge learning curves for new engineers to invisible backlogs of tech debt.
In this complete guide to writing meaningful code comments, we’ll:
- Explain what types of comments there are
- Discuss when and how to write code comments
- Cover some code commenting best practices
- Talk about when not to write them (and what to do instead!)
Should we be using comments at all?
Some say that if a piece of code is written well, we can do away with comments altogether because that code’s purpose will be obvious. As John Ousterhout says, this is a delicious myth.
Comments can indeed be used to plaster over poor or unclear code. But the bottom line is that various kinds of information simply can’t be represented in code.
Types of comments (and when to use them)
1. Contextual comments
Contextual comments are used to describe any purpose, intent or feature of code that isn’t obvious by looking at it. There are two primary types of contextual comments.
The first type are module-level comments. These describe the purpose of classes, functions and methods.
These comments might include…
- The parameters a function accepts
- The output it generates
- Any features of the module which can’t be understood at a glance.
The second type are logic comments. These explain code in context, wherever they are needed.
We should use them sparingly. The last thing your team needs are comments that explain things which are already obvious by glancing at the code!
Well-placed comments save development time and prevent engineers from misunderstanding the purpose or context of a code snippet. When we misunderstand code, it’s possible to introduce inefficiency, unnecessary complexity and bugs. These harm code quality and velocity, particularly when it happens routinely.
✅ When to use them:
- Where there’s unavoidable complexity in your code
- To add details to increase precision, such as units, inclusivity and exclusivity or the treatment of null values
- When context is missing, for example, using code from another repo or a package
For example, it’s unclear whether the `start` and an `end` parameters are inclusive or exclusive. A comment would save readers from working it out.
Fewer comments often mean more readable code. If somebody sees a comment, they’ll know it’s incisive and valuable.
❌ When to avoid them:
- When the intent of the function is obvious
- In libraries or APIs
- When it ought to be ticketed as an issue. (This is how teams get themselves buried under heaps of tech debt without even knowing it.)
Here’s an important point – if engineers are writing tonnes of comments to explain complexity, that could be a code smell.
An engineering practice your team should adopt is consistently documenting unnecessarily complex code as issues. This avoids accumulating serious tech debt that can be hard to pay back. If you’re not already using a tool for this, try Jira or Linear for app-based issue tracking. Or, try Stepsize to track and manage tech debt from your code editor.
2. TODOs/FIXMEs
TODOs and FIXMEs are everywhere. But they come with a substantial cost.
TODOs are a great single-player tool... They are great for temporarily dumping thoughts so you can focus on what you’re doing. TODOs are definitely better than having a list on the side, because they have context.
…But a terrible multiplayer tool. When TODOs make it to `main`, you end up with a never-ending, invisible backlog which isn’t actionable.
These bad comment practices are a direct path to spiralling tech debt and declining code quality.
Use TODOs to aid your personal code development process. Never push TODOs to `main`.
If you have TODOs in your codebase, use Stepsize’s issue management tool to convert them into issues without leaving the IDE. The tool will also surface TODOs in PRs, where you can turn them into issues.
Four best practices for writing code comments
1. Focus on the why
The best code comments are the ones you don’t need. The best comments can’t be replaced by code.
Check out this example we borrow from Jef Raskin:
/* A binary search turned out to be slower than the Boyer-Moore algorithm for the data sets of interest, thus we have used the more complex, but faster method even though this problem does not at first seem amenable to a string search technique. */
Code should be written as simply as possible. The best comments explain unavoidable complexity. For example, we might add a comment explaining the code's business application or the rationale for choosing a particular algorithm.
Code tells you how. Comments tell you why.
2. Don’t push TODOs. Create issues.
TODOs should only be single-player. TODOs that get pushed result in a never-ending, invisible backlog which isn’t actionable.
If you’re pushing a TODO, you should be creating an issue instead. When leaving TODOs in a codebase is a team-wide habit, it leads to spiralling tech debt.
Use a tool like Stepsize to manage issues without leaving your codebase. Stepsize integrates with platforms like Jira and Linear if you want it to. Learn how to get started with this game-changing tool here. This tool will enable you to:
- Convert TODOs into issues with a click
- Flag TODOs and issues in PRs
- Visualise issues and tech debt in different parts of your codebase
- Make issue management seamless for engineers
- Be a tech debt hero!
3. Pick and document conventions.
Documenting your conventions helps keep your comments consistent. This forces teams to decide what will be commented, and what the format will be.
You might be programming in a language with a document compilation tool, such as Javadoc for Java, godoc for Go! Or Doxygen for C++. Make use of these – even though they’re not perfect, the structural benefits outweigh the drawbacks.
Choose tools and practices for managing issues that are intuitive for individual engineers, and that delivers value to the team. Are you disallowing TODOs in `main` or having a policy on how issues are documented? Make sure this is universally understood.
Once good practices become habits across a team, they become an effortless contribution towards shipping faster, better code and managing tech debt properly.
Enshrining your principles pays back dividends when engineers join or leave your team, too.
4. Communicate better.
Shared understanding in software teams isn't all about comments.
Your team is more effective when everybody knows what is happening, without having to trawl through Slack or chase for Jira ticket updates.
We built CollabGPT to help.
CollabGPT knows everything that happens in Slack, Jira and GitHub.
It can meaningfully summarise what is going on, answer pressing questions and offers actionable suggestions on what to do next.
CollabGPT has long-term "memory". It's not just a dumb layer between an LLM and your data. It uses a complex network of AI agents to "remember" the context that matters about your projects.
Have a peek at CollabGPT, if you like
Rounding up
Code commenting isn’t just about knowing how to write good comments. It’s also about knowing when not to – and what to do instead.
When we get this right, we don’t just make it easier for others to understand. We:
- Speed up engineering velocity
- Improve code quality
- Boost team morale
- Manage and fix tech debt
Firstly, when we write good comments, we constantly surface essential information about code features and architecture decisions.
Secondly, when we avoid commenting about code problems and create issues instead, we ensure issues are actionable and visible. This promotes good tech debt management practices, which enable your team to fix tech debt issues.
Optimise the process of documenting and fixing tech debt issues in your team by using Stepsize’s issue management tool. It allows you to:
- Create, view and manage issues from your code editor
- Convert TODOs into issues
- Use powerful tools to visualise tech debt
- Understand how tech debt is impacting your codebase (and your team)
If you’re looking for a sustainable solution for tech debt management in your engineering team, you need Stepsize. It’s the world’s first and only bespoke tech debt management tool. And because it integrates with Jira, your PM will love it too.
Learn how to get started with Stepsize here. It works with Visual Studio, VS Code, JetBrains.