Recently, my team decided we had to tackle a number of problems with our aging application code base which were causing us pain. There were a number of technology and design problems which needed sorting out. The challenge for us was to continue to deliver new features while taking measures to not only stop the signs of rot, but also to make an effort to improve the code base. If we could improve the code where we were most active we would be able to deliver features more rapidly.
The first stage was a technical retrospective, facilitated by Liv, our project manager. The whole team got together and we brainstormed the problems with the code base which were slowing down active development and causing us pain. With everything hurting us out in the open, the team voting for the issues they thought were causing us the most pain. The focus was delivery, we weren't looking to make the code more beautiful for aesthetic reasons; we were trying to reduce friction to help us deliver more code more rapidly. With the key problems agreed, we moved on to discuss how we could fix them. So far so good.
Some of our problems could be fixed the traditional agile way write a technical card, estimate the cost and agree who would do the work and when.
Others were ongoing "try not to repeat this anti pattern" or "remember to do X" type solutions, so there was no action as such, other than to remember to not do "X" or to do "Y".
We also had a third class of problem. Long running repair or rework tasks. For example, the team has switched mocking libraries from NMock to Rhino Mocks. While we find we are more productive when we mock using Rhino mocks, we still have a legacy of old unit tests which periodically bite us when we need to refactor. The idea of scheduling a task to rewrite all our unit tests using Rhino Mocks is just plain wrong for a number of reasons. On humanitarian grounds, I couldn't bring myself to ask a member of my team to spend a week rewriting all our tests - we'd need to put her on suicide watch. Also, from a business value perspective, we only need to rewrite those tests which cover the active areas of the code. The application has been around for a while, and we are only adding features in some areas of the code. If we are not going to be slowed down by NMock riddled tests in a remote area of the code, why waste client money fixing them? Enter the bowling card.
The anatomy of a bowling card is very simple. The title should be a simple statement of intent. Here, we seek annihilation of all NMock calls in our unit tests.
In the middle is a grid, with a box for each step we need to take to win. In this case, we have a box for each test class which includes the NMock library.
Anyone gets to work on the bowling card, whenever the time is right. As a team you can decide when this should be. For this card the rules were that if you find yourself maintaining or extending an NMock based class, take the time to fix it by rewriting the tests to use Rhino mocks, or no mock at all.
There may or may not be a deadline. For this card, we don't have a deadline. There is no pressing need to replace all the NMock calls, only those in code hotspots.
You may or may not schedule the card to be played. For this card we didn't schedule work - there is no real business justification to do that. For another technical card which tracked removing inappropriate methods from a very large legacy repository, we were so slowed down by the problem that we did 'play' the card whenever we had capacity during an iteration. In this case there was clear business value completing the card would not only speed us up, but would allow bigger architectural refactorings to take place later on in the project.
The boxes are also very important. Each box is a step towards the goal. We take the steps in two stages.
Spares and Strikes
When a developer or a pair have fixed one of the steps locally, they get to draw a single line over the box, or a 'spare'. It is not converted to a 'strike' until the code change is checked in, built and tested if appropriate. We have found this to be really useful. The spare is a reminder to check in! With a lot of these tasks, we found it was easy to get carried away and bite off too much. Invariably this led to trouble, broken code and despair, leading to reverting the code and feelings of hopelessness. Once a pair has more than a couple of spares on the board, they start to feel the pressure to 'bank' and tend to check in more frequently.
We really like our bowling cards, they are great to remind us to make the world better, and to celebrate progress, no matter how small it feels at times. In fact Bernardo and Sinan started another card today to track legacy classes with inadequate unit test coverage - and there are a lot of boxes on that sucker!
Bowling cards have helped us improve our code. They remind us that we chose to fix something, rather than allow our code to tip. It is going to potentially take a long time, but we can all help by getting strikes when we have the chance. We also have a measure of progress that we can track, and proof that we are making things better one step at a time.