2022-01-09Hang your code out to DRY
I recently read an article by Jason Swett where he argues against Sandi Metz' old chestnut "duplication is better than the wrong abstraction" and it made me feel like I have to articulate why I'm Team Sandi here. I'm sure I've used the saying a million times in discussions with colleagues and, as such, I feel personally attacked by Jason's article and need to defend my honor.
You can go read the article first -- he spends some time thinking about what "abstraction" even means, but to me it's always been pretty clear since it's specifically pitted against duplication. You have a piece of code doing A, and you have a piece of code doing B, they're very similar, so you combine them into a single concept. On re-reading Sandi's original article it says kind of what I remember it saying, but it also... kinda doesn't? There's a lot more talk about programmers honoring the abstractions of elders who came before them, and less talk about the important part: how duplication can sometimes seem like a fantastic candidate for extraction and abstraction, but is in fact a nasty mirage that will strangle you in the dark and leave you slowly coagulating in a pool of your own technical debt.
The old man and the <b>
I think some of my conviction here comes from having spent roughly half my career (something like 2001-2011, get off my lawn youngsters!) firmly in the frontend camp. If you weren't around for the thunderous mayhem of the second browser war, this might be an interesting new fact to you: in Internet Explorer 6 and earlier, you were limited to a single class per element. We developed some pretty rad workarounds down in the trenches, while <font>
tags whizzed ominously past our heads, exploding in the distance: I remember stacking semantically empty divs inside each other to get more class names, and at some point I think I made a script that would create an IE-only include by turning classes like .foo
and .bar
into .foo-bar
in a colorful burst of combinatorics. (Don't judge me, I was young and needed the money.) Anyway, my point here is that composition in HTML was absolutely out of the window for almost a decade -- if you had a .box-with-header
class, well, that was it. That was the one you had. This was vindictive Old Testament stuff: no atoms to save you, ASP couldn't output a style tag to save its life, you were on your own and the browser was a hostile and fickle companion that would absolutely end you if you didn't know how to use the underscore hack or trigger hasLayout
in the right places.
So what did that mean for you when considering components? Well, you had to think both twice and thrice before committing to an abstraction that covered two or more cases. Oh, you found something that looks just like .box-with-header
? Well, if you cram it into the same abstraction you better hope it never ever diverges from that platonic ideal because if it does, hoo boy are you fucked. Your designer will come to you and say something like "this box needs a more pronounced border and the text has to flow around it, plus if..." and by that time you're already kind of checked out of the conversation because there's a loud ringing in your ears, and your mind keeps replaying that beach landing scene from Saving Private Ryan. Sure, we had Style Guides and rudimentary component libraries, but we also had fucking deadlines. People sometimes went down into the pit with a torch to refactor a handful of well-used classes, never to be seen again.
This inflexibility would mean the CSS quickly became append-only, never through any conscious process or decision, but because trying to reconcile changes to an abstraction that covered more than one use case was so enormously painful. We'd all nod and smile and pay lip service to the idea of re-use but in the day-to-day it was just a far better bet to duplicate code even if something seemed very similar. We'd only consider abstracting something if we were absolutely certain it wouldn't come back to bite us in the ass later.
Ghosts of the past
Well, we're in more modern times now, CSS was always a little weird about this stuff, and browsers have gotten a lot friendlier. But the funny part is that I've seen the same story play out in React codebases too: someone writes a nice and clean <Header>
component. Then of course it has to look different when you're logged in, and then yeah, it needs to change a bit when you're Admin, and oh there's another layout where we need to hide the main options when adding credit cards, and then someone realizes it shouldn't be fixed on the registration page and then Dark Mode and suddenly you're looking at spiders and cockroaches scuttling across a cursed 600-line component that's absolutely lousy with arguments and control structures. At this point everyone dejectedly agrees that it would probably be best to split it into several components. The next day, the lead developer quits. A story as old as time itself.
These days I mostly do Rails, and abstractions seem to come naturally to Ruby. It's part framework-related, since you're encouraged to think about your domain in terms of objects and what they're responsible for, and part language-related because Ruby is first and foremost a great OO language. It feels a lot easier to reach for an abstraction when you see something that looks like something else, and so, in Ruby land, we do. But unlike Jason in his article, I would urge caution: you should let your code walk a few miles in its own moccasins before trying to mash it up with something else. It's particularly important if you just wrote it! The fresher the code, the less you know about it. You haven't lived with it yet, its borders and neighbors are fuzzy, its requirements might change.
You have to hang it out for a while before it can be properly DRY.
Living with duplication is tedious but straightforward -- you make a change here, you might need to make a change there, too. Living with an ill-fitting abstraction is finicky, error-prone, and hard to understand -- if you make a change here, what does that mean for your other, similar-but-not-quite use case?
Over the years, I've come to see if
statements as kind of a code smell. If I find myself two levels into an if
statement, I'll lean back in my beautiful, well-weighted Herman Miller chair, leather creaking expensively, and I'll furrow my brows and grunt like a caveman. I just don't like 'em. You'll find a lot of if
statements in bad abstractions. Don't go there just yet: better to channel your inner Kabat-Zinn, live with the duplication until you can see the path forward clearly, and then place an exquisitely cut and molded abstraction on your concepts that you know will fit.