Blog

Sometimes the way forward is copy and paste

A shared function with a flag for every caller, and why I've started to think the kindest fix is to copy it back out.

I recognize functions by the signature. Six arguments. Three of the arguments are booleans. There’s a comment in the middle somewhere. “only for the export path.” A more recent comment below it. “also for the admin page now.” This code wasn’t meant to be this way. This code just grew. Like a sourdough starter. If you keep feeding it and never bake.

The inception story is always clean. Two pieces of code looked similar. Someone with a good gut feeling made them share a place. I’ve done it many times. I’d do it again. Another case appeared. It was almost the same. But ‘almost’ is a dangerous word in programming. The case gained a parameter. An ‘if’. A new case gained another parameter. We could make these changes in small increments. We could breeze through review without anyone squinting.

Flowchart: duplication, extract an abstraction, new cases add parameters and conditionals in a loop, until nobody can say what the code does; the way out is to inline it back.DuplicationExtract an abstractionNew case: add a parameterand a conditionalNobody can say whatthe code does anymoreInline it back into every caller.Live with the duplication; find the real abstraction later.again
How a shared abstraction goes wrong one reasonable change at a time, and the way out.

I think it lives because it looks like a job. Like it would be vandalism to remove that shared function. It has tests (sometimes). It has a lot of callers. It carries a glimmer of thoughtfulness from some decision that was probably made. And so we push it around. Because to push it around seems kind of respectful.

The weird fix is to regress. Paste the body of the shared code into each caller. Within each copy, rip out the stuff that caller never accesses. The result is often surprising and minimal. And sometimes the copies aren’t that similar after all. They only seemed like twins because they were wearing the same coat.

Which makes us nervous. Rightly so. We’ve grown up with “don’t repeat yourself.” And a diff that intentionally inserts two hundred lines feels like a sin. But I don’t see it as an indictment of code sharing. Code sharing is a prediction of the future. Sometimes the prediction is wrong. When the prediction is wrong, it costs more and more as people add flags. So the most economical day to acknowledge it is now. And then the next most economical day is tomorrow.

(I’ve also seen the mirror image mess: five copies of the same date parser diverging as far apart as the continents. I’m not arguing for duplication here. But it’s honest. You see it. You can count it. You can correct it when you have time. When the mess is a tangled abstraction, it buries its cost inside each change that involves it.)

How do you know you’re in one? The best indicator I’ve found is fear. When people don’t want to change a method. Because they’re afraid of the unpredictability. The tool has become a landmark. People avoid it. They add a new helper right next to it. And now there’s two.

Sometimes I think that the old “three’s a pattern” test is really more about patience than counting. Maybe two similar things is a fluke. Maybe three is a pattern. But you have to wait for the third. And that means a little bit of copy and paste. Which is about the least expensive thing in software.

Figuring out how to frame the retreat in such a way that the person reviewing it doesn’t see it as going backwards? I can do “unify these helpers” and get warm fuzzy reactions. Do “split this back apart” and get the arched eyebrow and “are you ok?”. Maybe it needs its own little icon. Sweep brooms, maybe?

Reply by emailEngineering

Start typing to search the blog.