We start every project with good intentions, but invariably our code unravels into a big ball of mud. It's disconcerting, and yet unavoidable. We sheepishly explain to the developer who joins the project 2 years in, "well, you see, back when we started, we just didn't anticipate X or Y or Z would change", and "we didn't have time to go back".
It can all start with a simple text box...
TextBox textBox = new TextBox();
Initially, that's all we need. But of course there's styling to be added, and also a label, so maybe we just tack on a few lines after each text box initialization. No big deal.
TextBox textBox = new TextBox();
textBox.setStyle("txt-standard");
Label lbl = new Label("First Name:");
TextBox textBox = new AcmeTextBox("First Name:", "txt-standard");
And there it is. This trivial example captures the essence of a problem that occurs over and over on every project, large and small, team or solo. The problem is: what do we do when we find a better way of doing things? Note that "better" can mean not just more maintainable, but more extensible, scalable, faster, etc. Of course the decision seems obvious (go with the better solution!), but it's not always that easy. Just consider your choices for the TextBox example:
1. Refactor: Go back to each TextBox initialization, and replace with the AcmeTextBox.
2. Live and Let Live. Use the AcmeTextBox on all new screens, but leave the old code as is.
3. Stay the Course: Stick with the old, clunkier TextBox initialization.
The lead developer will argue: if it's genuinely a better way of doing things, then let's go back and refactor the old for the new. Not only is it a better solution, but there's no worry that someone will inadvertently copy-and-paste from the old code, and propagate the inferior solution. (Option 1: innovation + consistency)
But "not so fast", the PM will say. If the screens functioned correctly before, don't touch that code! Regression testing costs time and money, and there are deadlines to meet. If you want the better solution moving forward, great, but let sleeping dogs lie. (Option 2: innovation + inconsistency)
Enter the curmudgeon developer: it'll just be confusing to do the same thing in two different ways, so let's just deal with a little extra typing and keep things consistent. (Option 3: no innovation + consistency)
So who's right?
The PM and the curmudgeon developer have valid points, there are costs to change, but if we heed their advice too often, it's a quick slide down the slippery slope into architectural mud. A codebase rife with inconsistency is a maintenance nightmare. So too is a codebase that never evolves.
In our gut, we want to listen to the tech lead, and always go back and refactor, but we realize this is a tough sell for all stakeholders. How do we make the case then? Well, we must show not only that it's worthwhile to implement the new solution:
Cost of Solution < Value of Solution(new code)
...but also that there's benefit to go back and refactor the old code. In other words:
Cost of Solution + Cost of Refactoring < Value of Solution(new) + Value of Solution(old)
The problem is, that while the cost side of the equation is usually a pretty tangible number incurred in the short term (e.g. it'll cost 20 hours to refactor all instances of TextBox to AcmeTextBox), the value side of the equation is neither easy to quantify nor realized in any near-term horizon (e.g. how much productivity is actually gained by using AcmeTextBox everywhere? And how do you convey this to the PM or QA lead?). Getting all stakeholders to buy into refactoring, therefore, is a non-trivial leap of faith: it must pay for something now in hopes of getting some return down the road.
What can we do? I think we need to address both sides of the equation.
On the left side of the equation, we need to to work vigilantly to reduce the cost of refactoring, by committing to continuous integration, unit tests, etc.. If it's cheap to refactor, then there's less of a burden to show that consistency is worthwhile.
On the right side of the equation, we can do a few things. First, we can try to use data to help quantify the value of consistency (i.e. maintainability) using tools like Sonar or Cruft4J, and also concepts like technical debt. If stakeholders understand the value, it'll be easier to weigh against the costs of refactoring. Second, we need to build trust by continuously delivering value to the business side, ensuring that refactoring expeditions are not done to the exclusion of tangible functionality. Philippe Kruchten presented an interesting approach for doing this using a color coded backlog.
In the end, the approach of going-back-to-implement-the-new-solution-everywhere won't always win the day. The cost of refactoring will be too high, or the value of consistency won't be appreciated. In these cases, you'll be stuck with the age-old software development dilemma: consistency or innovation. I usually side with the latter, but I'd love to hear your thoughts!