A consistent problem in software development is reading code. Reading code, like reading a book, consists of a few separate levels of understanding:
- What does the code say?
- What does the code mean?
- Why was that way of expressing that meaning chosen?
- How do I interact with the code?[1]
These are roughly in reverse order of specificity: “what does the code say” is the most specific and least informative question you can ask, while “how do I interact with the code” is one of the most high-level questions you can ask, and may not necessarily require a detailed understanding of the answers to the first three questions.
To understand what code says, we can simply read the code. That is the most fundamental level of understanding. Often, meaning is just as easy to glean. As when a book describes a character as “tall”, there's typically nothing more to it, so most code is initially fairly easy to grasp in terms of meaning. Notably, the more you grow as a developer and the more code and advice and such you read and write, the more code will fit into the category of easily understood meaning. Certain patterns that can be difficult to grasp the first or second time may become second nature over time, to where a simple glance at a piece of code quickly tells you exactly what pattern you're looking at and what it's meant to accomplish.
Properly written code should be written to expose what it says and what it means as clearly as possible. That's why we adopt relatively short functions, descriptive function names, etc. But sometimes even at that point, it's difficult to elucidate the meaning of code. That's when we apply comments. The key is that writing a comment should never describe (as is often preached) *what a block of code says*, but rather more *how* or *why* it does what it's doing—its meaning.
The problem with comments, of course, is making sure they're up-to-date. You don't want someone to change the code but leave the comment alone. This is the perennial documentation problem, and it's a hard one to solve. Part of the solution involves avoiding comments when possible—relying instead on clear code and well-named functions and variables to convey as much of the meaning as possible. When comments are very high-level, typically you only need to update them if you rip out an entire chunk of code, when you should hopefully be more likely to remember that the comment is associated directly with that block.
There is, however, another solution. Really, it's complementary rather than a replacement to using comments. That solution comes in the form of commit messages. Assuming you're properly using atomic commits, your commit messages can contain the answer to *why* a particular implementation or algorithm was chosen. A comment could grow outdated, but a commit message won't because, if a given bit of code is replaced, a new commit will be created, and that message will describe the reason why the second change was made.
Using solely commit messages for the whys and wherefores is probably not the best solution in every situation. There's question of immediacy: if the information you want to provide is something that you're likely to need every time you go over a chunk of code, maybe a comment is the better place to put it. But if you're trying to explain reasoning for a chunk of code that's hidden behind a good, descriptive function name, perhaps a commit message is a better place to put that, since if someone's reading the function they're probably about to make changes to it, and they'll have the time to invest in looking for your message as part of the process of understanding the meaning and purpose of the code.
Commit messages can probably form a large part of documentation if used properly, and perhaps if tools come to fruition that surface them more easily. One can imagine a version of reverse literate programming where the actual documentation exists in commit messages, and a tool that stitches the messages and code back together to create a coherent whole. I've played with the idea of structuring tutorials with tutorial contents in commit messages associated with the diffs of their commits—each step in the tutorial would be explained and described by its commit message. The project isn't quite usable, but it's an interesting experiment in using git features themselves for blogging, rather than just using git as a content repository for a blog.
Regardless of how hard you lean on commit messages as exclusive expositions of meaning and motivation, I think it's a good idea to write a good commit message that includes some of this information. It's somewhat redundant, yes, but commit messages are the quickest way to track the evolution of a file or block of code (or project). That makes them an ideal place to document those kinds of thoughts in a way that can be reviewed in bulk later when attempting to gain broader understanding.
I'd love to hear thoughts on this strategy, and perhaps alternate suggestions or other interesting ideas on how to deal with documentation getting outdated, and on how to leverage source commits as more than just a giant undo button (which just seems like a waste of a massively powerful tool).
[1]—There are more levels of understanding, of course, especially “what does running this code change?” Functional programming advocates want to obviate that question altogether by making the answer always be “nothing” ;) Arguably, understanding what code says and what it means is sufficient to understanding what it changes, as well.