LYNN
Adding the TODO macro to c
The usage of TODO comments
A common use case for comments are to add notes to remind themselves at a later date to implement, or rework, some functionality of the code. These are TODO
comments, and (in my usage at least) have the following structure:
/* TODO({myname}) {thing i want to do later */
An example of when this is useful is working on something but notice the API somewhere else can be improved, or is missing something that isn't critical to your current development. This is, in theory, a practical way of documenting these thoughts. It is certainly better than not documenting it at all.
So what's the issue with this method? Well, you have to go and do whatever is notated in the TODO
comment for it to be useful. The only time you are going to see these TODO
comments are when you are already working on something, and stumble across them in the code. This is no different than the case above, where you probably don't want to stop what you are developing or crowd your branch with unrelated enhancements.
So how can we improve workflow to better utilize these comments? I have found grep
to be a very powerful tool in these situations. Consider the following code:
/* test.c */ #define EAST_DEBUG #include "east.h" #include <stdlib.h> east_global const char shell_output[] = "dependency1 dependency2 dependency3"; int handle_dependency (east_string_view); int main (int argc, char **argv) { /* TODO(lynn) Refactor this portion of code. */ east_assert (argc < 3); if (argc < 2) { /* TODO(lynn) Write the usage text for this program. */ return 1; } east_string_view sv, dep; sv = east_make_string (shell_output, sizeof (shell_output)); while (sv.count > 0) { dep = east_string_next_chunk (&sv, ' '); printf ("%.*s\n", (int)dep.count, dep.data); handle_dependency (dep); } return 0; } int handle_dependency (east_string_view dep) { /* TODO(lynn) Handle adding dependency to the dependency graph. */ return 0; }
The density of TODO
comments is a bit exaggerated, but I hope you can get the point. In a real life situation, this would more likely be spread out over 10-20 files and not easily parsed by the human eye.
Now we can run a grep
command at the start of our workflow to check in on the TODO
tasks. It generates us a bit of a TODO list
that we can work through and check off:
cat grep -Rnw . -e 'TODO(lynn)'
./test.c:12: /* TODO(lynn) Refactor this portion of code. */
./test.c:16: /* TODO(lynn) Write the usage text for this program. */
./test.c:34: /* TODO(lynn) Handle adding dependency to the dependency graph. */
Great! This solves the issue of stale TODO
comments that never get seen again. So why is this page about a TODO macro
and not about TODO
comment?
A different approach to the TODO list
We have our TODO
list and we're ready to get started. There's only one problem: We have file names and line numbers from our grep
, but other than that the TODO
comments are completely disentagled from our codepath, and are instead ordered by their appearance in the source code. This is useful if we are setting out to tackle every single TODO
, but a more realistic case would be picking areas that are most relevant to what we are currently working on.
It would be nice if we could just launch our software, navigate to the piece of code we are working on, and have a list of TODO
comments specific to the area we are visiting. That's where the macro
comes in:
/* east.h */ #ifdef EAST_DEBUG /* extern void __east_assert (const char *assertion, const char *__file, int __line); */ extern void __east_todo (const char *message, const char *__file, int __line); /* #define east_assert(expression) \ if (!(expression)) \ { \ __east_assert (#expression, __FILE__, __LINE__); \ } */ #define east_todo(expression) __east_todo (#expression, __FILE__, __LINE__); #else /* #define east_assert(expression) ((void)(0)) */ #define east_todo(expression) ((void)(0)) #endif /* EAST_DEBUG */
I've commented out the unrelated lines of code from my library, east.h
. Above we define the east_todo
macro, which calls our internal library function __east_todo
. It passes along the message, along with two compilers macros: __FILE__
and __LINE__
, which pass along the file and the line the macro was called on, respectively. The implementation is very simple:
/* __east_todo -- push to stdout the todo reminder in the terminal color * yellow. This does not abort the program */ void __east_todo (const char *message, const char *__file, int __line) { fprintf (stdout, "\033[33m**TODO** %s [in:%s(%d)]\033[0m\n", message, __file, __line); fflush (stdout); /* needed for grep to function properly */ }
We do a simple print to stdout
, adding some yellow colors to draw the eye. Then the code continues on as normal. If we replace the comments from our example above with this macro, it looks something like this:
/* test.c */ #define EAST_DEBUG #include "east.h" #include <stdlib.h> east_global const char shell_output[] = "dependency1 dependency2 dependency3"; int handle_dependency (east_string_view); int main (int argc, char **argv) { east_todo ("Refactor this portion of code."); east_assert (argc < 3); if (argc < 2) { east_todo ("Write the usage text for this program."); return 1; } east_string_view sv, dep; sv = east_make_string (shell_output, sizeof (shell_output)); while (sv.count > 0) { dep = east_string_next_chunk (&sv, ' '); printf ("%.*s\n", (int)dep.count, dep.data); handle_dependency (dep); } return 0; } int handle_dependency (east_string_view dep) { east_todo ("Handle adding dependency to the dependency graph."); return 0; }
This example is, again, quite small and meant only for displaying the capabilities. I would ask you to imagine a much larger codebase. Let's run our code now without any input:
./test | grep TODO --color=never **TODO** "Refactor this portion of code." [in:test.c(11)] **TODO** "Write the usage text for this program." [in:test.c(15)]
And we see here the lack of inputs should prompt a usage
output to help the user understand what inputs they need to provide. The TODO
comments are contextual! Let's try again, but this time passing an input:
./test 1 | grep TODO --color=never **TODO** "Refactor this portion of code." [in:test.c(11)] **TODO** "Handle adding dependency to the dependency graph." [in:test.c(33)] **TODO** "Handle adding dependency to the dependency graph." [in:test.c(33)] **TODO** "Handle adding dependency to the dependency graph." [in:test.c(33)]
And here we see just how many times that particular codepath is hit. In the future I'll add capability to build the TODO
comment's string out, for example in this case adding the name of the dependency that isn't being properly handled.
Which is better?
My goal wasn't to come out and say that TODO
comments are somehow obselete. They are a staple in programming culture for a reason. That being said, if you have a relatively large hobby project that has spanned a few years: run a quick grep
to see how many un-finished TODO
comments you have laying around. I bet you will cringe at the number.
With the macro, I hope to reduce the amount in my own projects, and maybe it will be useful to you as well.