Adventures in refactoring: The Gambler’s perspective
December 2nd, 2016 4:12 pm Category: Optimization, by: Steve Cutler
Earlier this year I embarked on a project to do some major work on one of the applications that I inherited. I had been working with the code for several months and had a good grasp of how it worked, its layout, and all its main functionality. However, the overall architecture of the code and the way it was laid out was making it increasingly difficult to do any work on it. One of the main features was becoming particularly problematic and it happened to be the feature that got the most enhancement requests and bug fixes. With each bug fix and enhancement that was added, the code became more and more difficult to work with.
Around this time, my wife and I spent some time traveling with her grandparents. Her grandparents have a healthy love for “Country Western” music. Their house and cars are well stocked with albums from Merle Haggard, Johnny Cash, and Willie Nelson, among others. As we traveled with them, we listened nonstop to a compilation CD of Kenny Rogers’ Greatest Hits. We will never know for certain if we were treated\subjected to so much Kenny, because of their love of the album, or because they didn’t know how to work the “newfangled” stereo in their truck. My money is on the latter! One song on the album “The Gambler”, stuck out to me and some of its lyrics became a bit of a theme song for me as I continued to work on the project.
You’ve got to know when to hold’em,
Know when to fold’em,
Know when to walk away,
Know when to run.
These lyrics kept coming to mind as I was trying to figure out if a code refactor was really in our best interest. We had a customer for the software, and we had made several releases to them. By all accounts the software worked well and we were getting close to the end of a development phase. So it was tempting to just keep the code as it was and implement the last couple of requests. On the other hand, the code was so fragmented and difficult to work with that the task of squeezing in a couple of simple feature requests (that should have taken a couple of hours to implement) would take days to thread in. To make matters worse, these changes usually resulted in several bug fixes which also took more time than they should have. And this was on code that I was familiar with and actively working on! What would happen several months or a year down the road when we needed to fix a bug, add another feature, or launch a new phase of development on the application? Would we hold’em? Fold‘em? Walk away? Or would it be time to run?
Developers at heart like clean starts. With a clean start, you get to make the choice of using new technologies and trying out different development patterns. And I am no exception. However, I wanted the decision of what to do to be based on what was best for the company, the application and our customers, not my desire to start clean and toy around with groovy new technologies.
It turns out that the conventional wisdom is to avoid a rewrite at all cost, favoring instead small incremental changes to the code. This approach was described by Joel Spolsky CEO of Stack Exchange in his blog post “Things You Should Never Do, Part 1”. While the blog post is quite old, it is still valid and very heavily cited in other articles on this topic. In this mindset, the old code may be ugly, inefficient, and hard to read and understand but it is preferable because it has been tested more thoroughly, has been run on real-world computers by real-world users and contains real-world bug fixes for scenarios that aren’t always obvious. And focusing efforts on rewriting code from scratch may very well leave the door open for a competitor to eat your lunch.
While all of this makes sense, you should never say never. In a post on Stack Exchange Michael Meadows made some excellent observations on when it may be appropriate to do a full rewrite. He lists many things to consider including deployment concerns, new developer ramp up time on the project, project size, and automation among others. Some of the more salient points for my scenario were:
- Simple bug fixes take too long because of the complexity of existing code
- New Features take too long, and cost too much because of the interdependence of the code base
- The coupling of components is so high that changes to a single component cannot be isolated from other components
- A redesign of a single component results in a cascade of changes not only to adjacent components, but indirectly to all components
With all of this information in mind, the decision was made to focus our efforts on a major rewrite, not of the whole application, but on one of the main features that was also the most problematic. However, because the code lacked isolation of features, the cascading changes eventually made it so almost the whole code base would need some sort of change. In the end, the decision was finally made to walk away from the old code.
When trying to decide how to clean up ugly or cumbersome code, there are many factors to consider. And there may not be one right answer. There can be a lot of good reasons to avoid a full rewrite of the code and if you can make the needed changes in small incremental steps, that is most likely the best approach. However, there are also good reasons to consider a full rewrite of the code when incremental changes are more expensive and time consuming.
In the end, the Gamblers wisdom is true, you need to know when to hold’em, know when to fold’em, know when to walk away, and know when to run.