One of the core practices I encourage in developers when joining a new team is shaping changes into small, focused, atomic commits. For folks that are used to committing code in a haphazard, laissez faire manner this is sometimes dismissed as pedantic fussiness: if the code works, it works and that’s what’s important! Well I strongly disagree. Here I want to present a few of the ways taking the time to shape small focused commits help you to be a more effective developer and ship better software.
First things first, let’s acknowledge the fact that doing this well does take a certain level of tooling knowledge and practice. That means getting comfortable selectively staging changes (
git add --patch) and revising your working history as you go (
git rebase --interactive). But once you get the hang of it, it becomes second nature and not doing it will start to feel a bit icky, like skipping test coverage for a new piece of code, or not brushing your teeth before bed.
1. Slowing down
As well as tooling and practice, it also take a level of discipline. That’s because to understand how a piece of work might be delivered as small focused changes, you have to slow down and think about how it could be sliced up and delivered iteratively. This is actually the first benefit in disguise! Figuring out a plan for the work that breaks it down in terms of small, iterative steps is a useful strategy for making a large or difficult problem manageable.
I personally do this with a checklist of tasks that I write down before starting a piece of work. I then update and adjust the list as I progress through the work and my understanding grows and changes. The tasks don’t always map one-to-one to commits, but they often do.
@tomstuart talks more about this and other strategies for breaking down large and difficult problems in his talk Get Off the Tightrope.
2. Taking small steps
Whilst figuring out a series of small, digestible steps to tackle a problem can help get over the anxiety of a large or complex task, it’s not always possible to figure out the whole plan before you start; Sometimes you just need to take that first step. You might not be able to see the whole picture yet, but you see one small change that will get you a little closer. A small, focused commit is the perfect way to “bank” some progress, capturing any insight in the commit message, before continuing to chip away at the problem. Countless times I’ve found myself solving a seemingly complicated and difficult task this way, with only a rough idea of where I’m going, but edging ever closer to the solution one small commit at a time until poof!, the problem is solved.
3. Banking your progress
I love the idea of banking progress in neatly packaged focused commits. It’s a bit like reaching a save point in a video game. You feel a sense of progress. It gives you a perfect reset point if you happen to get lost (
git reset --hard HEAD). It also allows you to unload what you’ve learnt (in the commit message) and reset your working memory, freeing up brain cycles to tackle the next part of the challenge.
4. Spotting problems early
Making small, focused changes that you then wrap up in a commit also helps you get ahead of bugs and unintended side-effects. A banked commit is a perfect opportunity to trigger a build and make sure the change hasn’t broken something elsewhere, rather than waiting until all the work is finished and potentially face an avalanche of unforeseen failures that could have stemmed from any number of changes. Spotting and resolving a bug or issue is much easier when the surface area of the change is small and focused vs a 1000-line Hail Mary commit of unrelated changes. Which leads nicely into…
5. Easier debugging
With small focused commits you unlock the full power of Git’s secret bug-smashing tool:
git bisect. I’ve not had to use it very often, but when I have it’s felt like a super-power for finding the source of regressions and bugs. bisect will still work without small focused commits, just not quite as effectively.
I’ve successfully taught another person `git bisect` and used it to find an obscure bug in minutes that surely would’ve taken hours/days to find otherwise. If you find yourself saying “this used to work…” in your code, use `git bisect`.— Mitchell Hashimoto (@mitchellh) January 20, 2021
More about git bisect over in the docs.
6. Less painful merge conflicts
This one only dawned on me recently: resolving merge conflicts becomes more straightforward when commits are small and focused. Perhaps I assumed I’d just got better at doing it (which is still partly true; the more you do it, the better you get at doing it) but on reflection I’d now attribute most of it to those small, focused commits. Because each commit is small and doing one thing, it’s more staightforward figuring out what the delta between the upstream change and the one being applied in the commit should be.
7. Writing more disposable code
This one sounds weird, but changes that are shaped into focused atomic commits tend to be easier to revert cleanly, making code written that way easier to delete! You might need to do this whilst your still working on the feature, perhaps if you decide to change direction, or even much later in the future as the software evolves. As @tef_ebooks puts it: Write code that is easy to delete. Shaping changes into small and focused commits is one thing that will help you do that.
8. Bringing forward changes
Related to that, focused commits are also easier to pull out and use elsewhere. Maybe you’ve fixed a bug or performed a refactoring whilst still working on a larger piece of work. You can cherry-pick and ship those commits early to get the benefit now whilst also shrinking the size of your work-in-progress.
Spent a couple days working methodically through a Rails 6 upgrade, banking small, focused commits as we went. Made for calm, steady progress, with most of the commits shippable against Rails 5, shrinking the size and risk of the actual upgrade. Small, focused commits, always.— Tekin Süleyman (@tekin) January 21, 2021
9. Better code review
Saving one of my favourites for (almost) last, small focused commits lead to an infinitely better code review process, both for you and the reviewer. The reviewer gets the option to tackle the review in smaller commit-sized chunks, making it easier to review and less likely bugs will slip through.
For an example of what I mean, imagine trying to understand all the changes in this pull request in one go versus reviewing the changes commit by commit, with the commit messages as guiding commentary.
The reviewer’s feedback can also be more targeted, and because the changes are in focused commits, chances are you’ll have a simpler time incorporating any feedback into the existing commits (for those of you that aren’t afraid of or against rewriting the history of an open PR to avoid those “PR feedback” commits that add noise and distractions to the history).
10. A searchable history for future code archaeologists
I won’t go into this one too much as I cover it extensively in A Branch in time (a story about revision histories), but small focused commits with well-written commit messages make the software more maintainable long-term as they provide a commentary to the code’s evolution that can be searched by future developers to better understand the software and the way it has changed over time.
I’ll be writing more on the specific techniques that you can use to shape focused commits in the future. In the meantime if you want to explore these ideas further I’d recommend you check out the following talks:
- A Branch in time (a story about revision histories) a talk I’ve done illustrating some of these ideas
- Simplify writing code with deliberate commits by @joelchippindale
- Getting more from Git by @alicebartlett
More articles on Git