Consistency and Innovation: Pick One
What do you do when you find a better way?

August 19th 2013

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();
Label lbl = new Label("First Name:");

After creating a handful of screens (each with a dozen or so text boxes), an observant developer opines: "hey, we could save a lot of typing if we'd just create a custom TextBox wrapper which includes the style and the label", like this:

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!

Recent Posts

  1. Pair Programming - My Personal Nightmare
  2. The User Interface and the Halo Effect
  3. Velocity and Story Points - They don't add up!
I'm an "old" programmer who has been blogging for almost 20 years now. In 2017, I started Highline Solutions, a consulting company that helps with software architecture and full-stack development. I have two degrees from Carnegie Mellon University, one practical (Information and Decision Systems) and one not so much (Philosophy - thesis here). Pittsburgh, PA is my home where I live with my wife and 3 energetic boys.
I recently released a web app called TechRez, a "better resume for tech". The idea is that instead of sending out the same-old static PDF resume that's jam packed with buzz words and spans multiple pages, you can create a TechRez, which is modern, visual, and interactive. Try it out for free!
Got a Comment?
Comments (5)
August 19, 2013
Good post.

This is a good example where data-driven design/behaviours can help.

Suppose all the UI layouts are in JSON/XML. You could easily check for a label followed by a textbox, and change to the other AcmeTextbox. If the code runs based on some external definition, the code can change with lesser impact, and it can always be put back fairly easily.

August 20, 2013
I this kind of problems are frequent, especially in long lasting projects, where people come and go in the development team.
The Project Manager should put in balance the benefits of using the new approach and the effort of changing what has already been done. If the benefits outweigh the effort, then the new approach is beneficial and should be used.
August 20, 2013
Thanks for the comments.

@DavidM - Interesting point about data-driven design. I think in general, no matter how thoughtful you are at the beginning of the project, you will always find a better way of doing things down the road as you learn more. When you do, you're in this bind: go back and apply the "better" thing, take a hybrid approach, or just stay the course.

@Robert - Totally agree. It's up to us (as developers) to help him/her understand/quantify the benefits of the new approach though...because maintainability (or other "ilities") for someone who's not in the code can be a pretty fuzzy concept.
August 23, 2013
Great post Ben! Really thinking through how to reduce the effort up front to make these arguments easier is a great strategy.

Here's a wrench to throw in the gears: sometimes you think it will be simple and it's not. You run across a bunch of cases that are 85% like each other but 15% different. So you start designing more and more complex constructors or solutions or abstract classes. Eventually, and with good design, this can all be cleaned up, but it increases the size of the left hand of the equation.
September 18, 2013
@Brian - Thanks! Really good point. I live this too often (sadly!). The corner cases I predict, and try to design for, are not always not the cases that arise a year down the road. Experience has helped me, but refactoring is always necessary.