Tonight I dreamt of Giorgio and Danilo. They were ticking me off. They were upset. The build was broken and they had just found a bug, introduced months before by me while refactoring some remote legacy area of our codebase. They were speaking words of reproach about how hard it would be to fix the bug now, being that code as legacy as untested code can be. They were blaming on me because I had introduced a bug in a hard-to-manipulate area of the code. How hard would the fix be now? Why, should I have ever refactor that untested code at all?
Just a few moments before waking up – or so it seemed – that apparent nightmare turned back into a dream: I had just committed very few lines of code at once throughout my refactoring thus it was very easy to backtrack my code change, get a very clear view of it and revert it. I got the solution, we learned a bit about our legacy code, thought I still kept my reproach.
Flashback to a few years ago.
I was refactoring a huge legacy codebase. The company I was working for had very very very very high maintenance costs due to the age of their codebase and the proud absence of automated tests. I wrote ‘proud’ because everyone was aware of the mere existence of automated testing technology but no one was even trying to introduce the benefits of automated testing in their daily life: “eh”, they kept on saying, “someone should do it sooner or later”.
I decided to do it.
The problem with nasty legacy code is that often you have to refactor it before you may even consider introducing automated tests. That requires developers to be very careful performing very small step-by-step refactoring to work their way to the first automated tests. In that case it was even worse. (Legacy) security measures used to prevent bots to hijack the (legacy) web interface was making my job harder than hard: it was impossible to put some end-to-end testing in place, my usual first step to secure a legacy codebase.
I was not wise enough and I “broke the build”, though actually there was no automated build to break yet! The editing step was too big to be easily reverted and I disrupted the whole team activity. For 2 or 3 days the team has been complaining about my “refactoring fixation” and – as per standard policy – I was punished: I had to bring croissants for everybody the day after and I got a refactoring embargo, forbidding me any further refactoring for months.
Compare my past experience and my dream of today. What changed? What has still to change? I think I learned two main lessons, at least.
Continuous integration as an individual policy
Continuous integration is a personal policy first, then a team one, last an automated one. Keep it small, baby steps, allow for some observation time after pushing some virtually dangerous code into production. Nasty side effects could arise and you want to understand their root cause in a few seconds. Small meaningful commits are paramount and I really mean them:
- Small so that is easy to scope them into your brain.
- Meaningful so that their abstract consistency is kept high. It makes no sense committing half of a bug fix together with half of a refactoring.
Limit your work in progress, reduce the scope of your action. You don’t pay a per-commit fee so commit profusely! Also, being able to split your big problem into smaller ones is very good clue of your understanding of the problem.
A few years ago I was daring too much at once and playing with a way too big refactoring all at once. In the commit of my dreams (literally :-)) nowadays I just edit a few very consistent lines of code.
Don’t punish your volunteers.
I was in a team. The team was working in a very ill environment. I dared to try fixing our daily life. I succeeded in many places. I was wrong once. I got to pay a €20-equivalent fee in croissants and I was required to accept the status quo.
How. The hell. Does it. Make sense? No sense.
Well, I mean, I don’t expect everyone to be happy and congratulate with me for breaking production code. Neither I think that human actions should be judged only by their intentions: I may have the best ones, but a damage is a damage. Just don’t ask me to accept a rotten status quo just because that’s the only way to go, just because you don’t care enough to fix it.
All in all, you know, only those who do something are those that make mistakes. If you punish people for being wrong while trying to improve, you’ll only make them less willing to improve.
You may ask: “They shouldn’t have blamed on you. They shouldn’t have tolerated errors too. What do you think they should do then?”.
I think an healthy team builds a fault-tolerant environment up together to nurture an experiment-oriented atmosphere. I expect my teammates to build upon each other’s mistakes, analysing their root-cause and setting a new standard every day. A few years ago I expected my team to join me in the refactoring. In last night’s dream I expected my teammates to understand that introducing a bug while refactoring untestable code is as likely as in hearth surgery is to have a patient in atrial fibrillation: it’s bad but may always be a part of the game.
And fortunately you don’t even need to dream about Giorgio and Danilo to agree, do you? 🙂