How often do you commit? Once a week? Once a day? Once an hour? Every few minutes? The more often you commit, the less likely you are to make a mistake.
I used to work with a guy who reckoned you should commit at least every 15 minutes. I thought he was mad. Maybe he is. But he’s also damned right. I didn’t believe it was possible: what can you actually get done in 15 minutes? At best, maybe you can write a test and make it pass. Well good! That’s enough. If you can write a test and make it pass in 15 minutes, check that sucker in! If you can’t: back it the hell out.
But I’ll never get anything done!
Sure you will, you just need to think more. Writing software isn’t about typing, it’s about thinking. If you can’t see a way to make a step in 15 minutes, think harder.
Chase the Red
One of my oft-used anti-patterns is to “chase the red”. You make one change, delete a method or class you don’t want – and then hack away, error by error, until the compiler is happy. It might take 5 minutes. It might take a week.
It’s very easy to get sucked into this pattern – you think you know where you want to get to: I have a method that takes a string – maybe it’s a product SKU from the warehouse; I want to replace it with a StockUnit which has useful functionality on it. Simples: change the signature of the method to accept StockUnit instead of string and follow the red, when the compiler’s happy: I’m done.
Trouble is – how do I know that every calling method can change? Maybe I think I know the code base and can make a reasonable guess. Maybe I’m even right. But it’s unbelievably easy to be wrong. I might even check all references methodically first, but until the code’s written – it’s just a theory. And you know what they say about the difference between theory and practice.
Is there a different way? What if I add a new method that accepts a StockUnit and delegates to the original method? I can test drive adding the new method, commit! I can go through each reference, one by one: write a failing test, make it pass by calling the new method, commit! Step-by-step, the code compiles and the tests pass every step of the way.
What happens if I hit something insurmountable half way? Where I am is a mess, but it’s a compiling, green mess. If I decide to back out I can revert those commits. Or refactoring tools can inline the method and automatically remove half the mess for me. But the critical thing is, every step of the way I can integrate with the rest of the team, and all the tests pass in case I hit something unexpected.
The trouble with long running branches (and I include in that your local “branch” that you haven’t committed for two weeks!) is that the longer they run the harder they are to integrate. Now sure, git makes it easier to manage branching & merging – but you still have to do it. You still have to push & pull changes regularly – let others integrate with you and integrate with others as often as you can.
The likelihood of you having merge problems increases rapidly the longer your branch is open. If it’s 15 minutes and one test, you know what? If you have a merge problem, you can throw it away and redo the work – its only 15 minutes!
Just keep swimming!
But if you’ve been working for days and you hit merge hell, what do you do? You’re forced to wade through it. What you’re doing is chasing the red in your version control system. You’ve adopted an approach that gives you no choice but to keep on hacking until all the red merge conflict markers are gone from your workspace. If it takes 5 minutes or 5 days, you’ve got to keep going because you can’t afford to lose your work.
The kicker is by the time you’re done merging and everything compiles and works again, what’s the betting some other bugger has jumped in with another epic commit? Off we go again. You’re now in a race to try and merge your steaming pile before anyone else gets in. This is a ridiculous race – and who wins? Nobody, you’re still a loser.
Instead, if you adopt the 15 minute rule, you write a test, make it pass, check it in and push to share with everyone else. Little and often means you lose little time to merge conflicts and instead let your version control system do what it’s good at*: merging changes!
* doesn’t apply to those poor suckers using TFS, sorry
An interesting side effect of a short cycle time is that you limit the damage periods of brain fade can do. I dunno about you, but there are times when I really shouldn’t be allowed near a keyboard. The hour after lunch can be pretty bad – a full belly is not good for my thinking. The morning after the night before: it’s best if you keep me away from anything mission critical.
The trouble is, if I have a multi-day branch open I’m almost guaranteed to hit a period of brain fade. Because I’m not committing, when my head clears and I realise I’ve screwed up I can’t just revert those commits or throw my branch away entirely – I have to try and unpick the mess I just made from the mess I already had. It’s incredibly hard to be disciplined and tidy when you’re wading in excrement.
We all make mistakes – but version control gives me an overview of what I did, without relying on my flaky memory. Maybe I’ve gone completely the wrong way and can unpick parts of what I’ve done, maybe I got lucky and some commits were half-way decent – I can cherry pick those and start a new branch. Without version control I’d be flapping around in an ungodly mess trying to figure out how to keep the good bits without just binning everything. All the time trying to explain to the project manager how I’m 90% done! No, really! Nearly there now!
Version control is an invaluable tool that we must learn to use properly. It takes discipline. It doesn’t come naturally. If you’ve never worked in such short cycles, work through a kata committing regularly. Our natural instinct is to type first and think later. Stop! Think about how you can make an increment in 15 minutes and do that. If you can’t: revert!