Adding context to your code

Context is key to helping us understand our software, especially when we look at that software later. Comments and commits occupy an important space in how we understand what we’re building.

Code

The code itself is the most immediate thing we can use to figure out what’s going on. Code is a versatile tool, and it comes with some context baked in! Since code is a series of instructions, it’s really good at answering the questions “What are we doing?” and “How are we doing it?".

We try to write good and readable code with understandable variable names, intuitive logical flow, and separation of concerns. However, software is often complicated; the problems we’re trying to solve are often complicated. It’s not always possible to keep all of everything in your head at once. The older and larger the codebase becomes, the less likely it can be understood entirely on its own. Even the most well-written code sometimes needs a little clarification.

Commits

One way we can add that clarification is with commits! Below I have an example of some local Git commits for adding a new feature to a web application:

- WIP Add notification component
- WIP Add function to allow update
- WIP Fix edge case for invalid value
- WIP Oops forgot to check in a file
- WIP Add more tests
- WIP Clean up comments

Each commit serves as a small reminder of where I’m at while I’m working. I also often leave myself lots of temporary notes in the commit descriptions (not shown in the example). If I have to work on something else or come back to this feature later, I have a good idea of where I left off. These individual work-in-progress commits make sense for me while I’m working on this, but they probably won’t make sense six months from now. Also, the app probably doesn’t 100% work at each point along this commit history; there could be failing tests or gaps in the implementation that only got fixed in later commits. So once I’ve finished up all my work and everything is ready to go, I squash my commits into a readable format before I open a pull request, so that the commit history is more immediately useful to anyone else that needs to read it:

ticket_number: Add new notification component
    - Services can now display a notification below the top menu.

Once this commit is merged, it becomes historical context for me and everyone else who reads it later.

A good commit message contains a meaningful summary of the changes it covers, but it also provides context about when something changed and who changed it. If you want to figure out when some behavior was added to a system, you can look at the commit history and find out. If you have questions about some particular part of code that perhaps isn’t well-documented, you can see who committed that code and now know who might be able to answer those questions.

Commits on their own can be useful and helpful, but commit history has some potential limitations. Sometimes you can lose the commit history entirely if files are renamed or relocated or extracted from a different repository. You might not even have all of the history if you’ve only done a shallow clone of the repository. And unless you’re using a tool or plugin to view them, commits aren’t immediately visible in the source code, so if you only put important in formation in the commit messages, it might be overlooked.

Comments

While commits are good for historical context and change over time, comments are good for current and relevant context. Sometimes comments are documenting a function or indicating an unfinished task, but they’re also often answering the question: “Why?". Why did we do something a certain way? Why didn’t we do something another, seemingly more obvious way? Comments help reveal design and implementation decisions that aren’t immediately apparent to the reader. Good comments save time for the next person that comes along by providing them with information that is relevant to that particular part of the software.

Here’s an example:

isNotificationEnabled() {
    return this.get(`isNotificationEnabled`) !== false;
}

At first glance, to me, this function looks like it doesn’t need to be this complicated. If we know from elsewhere in the code that isNotificationEnabled is a boolean, it can only be true or false, right? Why don’t we just return the value directly? The function seems like it could be written like this:

isNotificationEnabled() {
 return this.get(`isNotificationEnabled`);
}

There’s probably some strange edge case that causes problems if we do it the “simpler” way, but because there isn’t a comment on this function, it isn’t immediately clear what that edge case is or why this implementation decision was made.

Separate documentation

The next step to providing context for our code is writing documentation. Comments are essentially a specialized subset of documentation. Good documentation fills the spaces that can’t be covered by code, commits, and comments. Diving into the nuances of good documentation would make this post far too long, though, so I’ll stop there for now.

Provide clarity

I hope this post has given you some things to think about, and I hope it’s reminded you of the importance of adding a little more clarity for the next person that comes along, even if that person is you.