Leaving TODO comments in shared codebases is generally discouraged[0] and for good reason. For one, TODOs are typically used to tag issues needing resolution and it's unprofessional to leave a mess behind for your team to clean up. Moreover- to borrow a metaphor from the fantastic book The Pragmatic Programmer[1]- leaving TODOs lying around instils a sense of neglect and disrepair, in the same way that "broken windows" in an apartment building might elicit a similar feeling.
In this context, it's easy to see why people dislike TODO as a comment tag; it reminds them of occasions where they had to clean up other people's bad, TODO-ridden, code. As such, many view it as a cop-out that lazy / incompetent programmer's use to justify merging their code before it's ready.
Another perspective
I don't use TODOs terribly often, but I feel that they're unfairly maligned and are actually pretty useful when used within the confines of one's own feature branch.
In my mind, the uses of TODOs are twofold:
To track issues in your code that must be resolved prior to merging
To defer to your future self.
The first use is fairly obvious but the latter is, in my opinion, actually quite profound.
Why is deferring tasks to your future self useful?
Many problems will simply cease to require attention as your solution evolves.
For example, consider a method that needs documenting. You take a good 10 minutes to write a beautiful docstring, replete with examples and detailed descriptions. If in future you decide to refactor this method away, all of this hard work will have be for nothing (even worse, you may be unduly attached to your function and decide to not refactor it away when doing so is in fact the correct decision!).
Defer these sorts of problems to future you - they know whether or not the function was deleted as part of your refactor or not!
Refactoring and documentation are two tasks that can often benefit the most from being postponed until we're confident that our solution is close to completion. This is guaranteed to save us time and effort.
Strike while the iron is... cold?
Certain tasks typically benefit disproportionately from being postponed.
Refactoring and documentation are, again, emblematic of this.
As you spend time developing a solution it's inevitable that you'll acquire a modicum of domain expertise that you previously lacked. Refactoring once you have formed an adequate mental model of the domain and problem space is both easier to perform and leads to simpler, higher quality code. Similarly, writing documentation - let's use the example of a a function - whose implementation and purpose you fully understand is infinitely easier than trying to do so immediately after writing the function header[2].
Why TODO's?
TODO comments are simply a tool developers use to track tasks that have been deferred until a later point without having to store them in their head. Since they live in the code, they're impossible to forget about but typically they're low-level enough that they don't require tracking on an external issue tracker.
Many developers have internalized these lessons and instinctively understand when the time is right to perform these activities. These developers may still find TODOs useful for tracking must-resolve issues in their code.
Conclusion
For those who haven't, I would encourage you to try deferring one or two tasks a little using TODO comments (be sure to clean them all up though!).
Keep up the good fight and may all of your deferred tasks resolve themselves of their own accord!
Thank you for reading this, I sincerely appreciate it.
Notes and Refs.
[0] Thomas Guest: The case against TODO.
[1] Andrew Hunt and Dave Thomas: The Pragmatic Programmer: From Journeyman to Master
[2] I believe this is most likely the reason there is so much useless documentation out there that simply repeats the function name verbatim. I humbly ask that you do not document code like this:
def squares_the_circle():
"""Squares the circle."""