I've been thinking about the consequences of the Wrong Abstraction. My RailsConf 2014 "all the little things" talk included a section where I asserted :
duplication is far cheaper than the wrong abstraction
And in the summary, I went on to advise:
prefer duplication over the wrong abstraction
This small section of a much bigger talk invoked a surprisingly strong reaction. A few folks suggested that I had lost my mind, but many more expressed sentiments along the lines of:
The strength of the reaction made me realize just how widespread and intractable the Wrong Abstraction problem is. I started asking questions and came to see the following pattern:
Programmer A sees duplication.
Programmer A extracts duplication and gaves it a name.
This creates a new abstraction. It could be a new method, or perhaps even a new class.
Programmer A replaces the duplication with the new abstraction.
Ah, the code is perfect. Programmer A trots happily away.
A requirement changes such that the abstraction is no longer identical for every case.
Programmer B gets tasked to make this change.
Programmer B feels honor bound to continue forward with the existing abtraction, but since it can't work the same for every case they alter the code to take a parameter, and then add logic to conditionally do the right thing based on the value of the parameter.
What was once a universal abstraction now behaves differently for different cases.
Another new requirement arrives.
Another additional parameter.
Another new conditional.
Loop until code becomes incomprehensible.
You appear in the story about here, and your life takes a dramatic turn for the worse.
Existing code exerts a powerful influence. Its very presence argues that it is both correct and neccessary. We know that code represents effort expended, and we are very motived to preserve the value of this effort. And, unfortunately, the sad truth is that the more complicated and incomprehensible the code, i.e., the deeper the investment in creating it, the more we feel pressure to retain it (the sunk cost fallacy). It's as if our unconscious tell us "Goodness, that's so confusing it must have taken ages to get right. It must be really, really important, and it would be a sin to let all that effort go to waste."
When you appear in this story in step 8 above, this pressure may compel you to move forward, that is, to implement the new requirement by changing the existing code. Attempting to do so, however, is brutal. The code no longer represents a single, common abstraction, it has instead become a condition-laden procedure which interleaves a number of vaguely associated ideas. It is hard to understand and easy to break.
If you find yourself in this situation, resist being driven by sunk costs. When dealing with the Wrong Abstraction, the fastest way forward is back. Do the following:
- Re-introduce duplication by inlining the abstracted code back into every caller.
- Within each caller, examine the parameters that got passed in order figure out which bits of the inlined code you actually use.
- Delete the bits that aren't needed for this particular caller.
This removes both the abstraction and the conditionals, and leaves you with only the code necessary for each case; now each caller contains just the code it needs. When you rewind code in this way it's common to find that although each caller was ostensibly invoking the same abstraction, they were actually running very different code. Once you completely rewind the old abstraction you can start anew, re-isolating duplication and re-extracting abstractions.
I've seen problems where folks were trying valiantly to move forward using the Wrong Abstraction but having very little success. Adding new features was incredibly hard, and each success further complicated the code, which made adding the next feature even harder. When they altered their point of view from "I must preserve our investment in this code" to "This code made sense for a while, but perhaps we've learned all we can from it", and gave themselves permission to re-think their abstractions in light of current requirements, everything got easier. Once they inlined the code, the path forward became obvious, and everything got faster and easier.
The moral of this sorry? Don't get trapped by the sunk cost fallacy. If you find yourself passing parameters and adding conditional paths through shared code, the abstraction is incorrect. It may have been right to begin with, but that day has passed. Once an abstraction is proved wrong the best strategy is to re-introduce duplication and let it show you what's right. Although it occasionally makes sense to accumulate a few conditionals to gain insight into what's going on, you'll suffer less pain if you abandon the Wrong Abstraction sooner rather than later.
When the abstraction is wrong, the fastest way forward is back. This is not retreat, it's advance in a better direction. Do it. You'll improve your own life, and the lives of all who follow.
The public POODNYC (POODNIC!!) course is sold out, and scholarships have been awarded. Thanks again to Hashrocket for sponsoring; this is a tangible sign of their commitment to the community, and I am very grateful.
2016 Course Schedule
I regret to say that I'm happy to report :-) that my POOD course is so popular that next opening is the week of June13, 2016. No pressure, but 2016 is filling up; if you'd like a private course next year, we should talk about it now.
99 Bottles Book
The 99 Bottles book is coming along. I'm writing every day, and very pleased with how it's turning out. I still fear to announce a date for the beta, but it will certainly be this year. For announcements and more in depth updates, signup on the 99 Bottles mailing list.
When the beta comes out, there will be a limited number of extremely cool T-shirts. Just sayin'.