TODOs: When Unfinished Code is Good, Actually
Mar 26, 2024 #engineering #principles #coding
Lately, I've been thinking about how software engineers rarely complete projects, and as a result, code is often filled with "TODO comments." This is okay! TODO comments are beneficial as long as they do not become a crutch. I took a poll on Mastodon before posting this, and everyone agreed:
In this post I'm going to explore what TODO comments are good for and when they become a crutch.
TODO comments are common, but for clarity, they're the little comments in codebases that look like the following:
// TODO (jake): responses are not yet sorted. // See: doc/sorting-system for more info.
Why are TODO comments good?
They enable you to:
- Minimize the size of changes: Keeping changes small is crucial in software engineering. Smaller changes make it easier to identify bugs, gather user feedback, collaborate with teammates (reducing merge conflicts), and enable engineers to tackle the most important changes first. TODO comments help us create the dividing line between a change and another one following it.
- Explain long-term deferred work: Software engineers often defer work indefinitely due to the pareto principle (80% of the outcomes come from 20% of the efforts). It makes sense to defer unvaluable work. TODO comments aren't intended for planning new work, but unlike design docs or backlogs, TODO comments help you discover and understand forgotten deferred work during organic development. TODO comments can link to other documentation, and besides, it's a lot easier to be held accountable during code review to leave a comment.
Pitfalls
There's a few pitfalls to consider when using TODO comments:TODO comments can lead to cognitive overhead. They can become constant reminders of what doesn't work, little "tombstones" in codebases that continuously remind you of peril.
There's not a lot of incentive to clean them up. I recall one time someone added a lint checker to a codebase I was working on and it began to cause all our code reviews to fail. The lint checker had found debugging code that hadn't been cleaned up:
// TODO: Delete this when the product ships System.out.println(“Debug info: %s”, data);
The product had shipped 2 years ago! That engineer had left the team and successfully handed their cleanup responsibility off to future teammates. On the plus side, at least we knew why the println
was there when we deleted it.
They can be used to justify skipping critical features (aka "We'll get to it later!"). They are intended to either temporarily minimize change size or denote work that wasn't a priority, but sometimes priority work falls between these cracks. I've observed engineers justify skipping work temporarily, only to move the goalposts without fully communicating the change in scope to their team.
// TODO: I have written // the bug // that is in // the database // and which // you were probably // unaware // of // Forgive me // Promotion was // so sweet // and so fast
I'll give you an example: there was one time I was working on a project that was just about to ship, and we were seeing some odd behavior. I found TODO comments that perfectly described the issue we were seeing. I was simultaneously grateful and furious. The most insulting part was that the comment directed you to find other TODO comments. It looked something like:
// TODO: This code doesn’t work. // Find all 6 of my TODOs to answer the riddle of why.
Understanding the nuances of why that set of TODO comments exist and how to effectively resolve them created a lot of project risk. These 6 other TODO comments that pockmarked the code felt a bit like a soul jar from D&D or Voldemort's Horcrux from Harry Potter. They unnaturally extended the tenure of the previous engineer until you eliminated them all.
Mitigating Risks
TODO comments need not become a burden! The main tool to mitigate the burden of TODOs is to ensure a cleanup phase is included in project scopes. Communicate early with product owners the need to address outstanding code cleanup work. During cleanup phases, spot-check old TODO comments by deleting obsolete ones and updating vague ones. Leaving some is acceptable, as many products outlive their unresolved issues. A good practice is to timebox this work; it can be really tempting to solve every TODO comment left in your codebase, but it's often not worth it. If any TODO comment does lead to weeks or months of extra work, it's a good idea to discuss why this is happening as a team.
When engineers leave a team, they can audit their TODO comments for critical gaps worthy of documentation—hopefully this practice isn't too helpful. It can be pretty satisfying, though. If anyone ever fixes a TODO comment on a product I've departed, I'd love it if they let me know.
Does your team let you submit code with large, unfinished features? What’s the worst TODOs you’ve seen? What are your team's practices around documenting work remaining after a project finishes? I’d love to hear your experiences, so please share in reply to this post!