“I’m stuck on this one last test, got some time to pair today?”
This question gets asked every few months on the job. I always smile to myself because my hunch tells me we’ve usually got more than just one test to pair on. What I hear is, “I’ve almost got this Hail Mary done, but I can’t quite hack around the hacks, so do you know the one last hack to get this to work?”
So we dive in. We shave a few yaks. We eventually get the code to a spot that works and will be maintainable. We ship the code and the customer is happy.
With most of these scenarios, there’s always one practice that I see help folks early in their career. This is the practice of keeping every commit we do green (tests passing), even on our feature branches. This blog post will explore this practice.
Like any practice, this is a tool. This should not be applied in every situation. But giving this a shot (and getting good at it) can make you a great software engineer. I’ve found it to be a great default for my own work.
So let’s dive in…
Where Can We Revert To?
Software development—whether it’s building a new feature or doing some refactoring— is often a process of:
- Let’s try to do thing X.
- Whoops, that will be easier if we do Y first.
- Do Y.
- Do X.
Make the hard change easy, then make the easy change.
So when we’re working, the ability to stash a thought and quickly pursue a different path is important. We need to be able to fall back to a last known good state with passing tests.
For my own working style, I never want the last known good state to be more than 5 or 10 minutes away.
Getting into the practice of thinking and developing iteratively at the smallest scales usually makes for a much more pleasant software development experience. I shoot for making a commit every few minutes.
When we have a long string of red commits or no commits at all of work-in-progress, we work without a safety net. If and when we pursue the wrong path, we are throwing out hours or days of work. If the commit that we’re reverting to is red, we have the mental overhead of “Okay, the state that the code is in is wrong in ways A and B. I need to remember that.”
To have a more predictable software experience, we want to keep our commits small and green.
By keeping all commits green, we create some nice upside for ourselves when it comes to the work that we do. By keeping all commits green, all of our work is potentially landable.
This means that deciding when to open a pull request is a matter of taste. Once we feel like things are “about right,” we just open a PR. If we have 6 total commits that we’ll perform, we might cut 2 or 3 pull requests. How we land our work is separated from how we develop it.
I like to think of work done this way as an infinitely-long string of commits that happen to be broken apart into pull requests.
While this practice is interesting, it’s important to explore what principles might be at play here. If we take a look at the principles and values from Extreme Programming Explained, this practice aligns with the principles of Baby Steps and Flow.
Baby Steps asks, “What’s the least you could do that is recognizably in the right direction?” When we take small steps where all steps are green, we ensure incremental progress. We have the safety to revert something without discarding hours or days of work.
Flow says that larger chunks create more risk. We want to minimize risk to keep our value delivery consistent. When the risk is realized, our flow is disrupted.
By examining principles of software development, we get context as to how our practice works. We also get a better idea of when it won’t work.
The practice of keeping all commits green, like any tool, will have an appropriate time and place for use. Generally, I’ve found its costs to outweigh its benefits when doing exploratory spikes.
Specifically, if the code I’m writing is merely exploring an API, library, or problem space, it’s unlikely my final code will look anything like what I’m writing. It’s just fumbling around in the dark. Once it becomes clear how it needs to be built, I throw away the code and engage in keeping the commits green.
Keeping the commits green in exploratory work adds unnecessary steps and confusion to the process.
Applying the practice in this scenario requires having enough time to build one to throw away.
There is another downside: It can sometimes be difficult to ensure your green state is actually green. Usually, I will run a subset of the test suite while working on a certain area of code. This is more commonplace in a large code base with a large test suite.
There is a rare occasion that my set of local tests don’t catch a regression that other tests catch. This usually doesn’t happen that often with well-structured tests, maybe 1 in 100 times. Brittle tests can increase this number.
This practice was extracted from exploring test && commit || revert (TCR) pattern. TCR keeps all commits green by default, but I’ve found myself unable to go “whole hog” and embrace the automatic commits. It’s also substantially more difficult to practice when the test suite takes a few seconds.
Stealing the kernel of the idea of TCR has resulted in my practice of keeping all commits green.
The practice of keeping all commits green while developing production code can lead to a smoother development process. It caps the size of false starts and allows us to backtrack easily.
If you are earlier in your career, try adopting this practice to see if it works for you.
Special thanks to Iheanyi Ekechukwu and Jeff Carbonella for reading early drafts of this post.