DRY is the first of the big rules engineers learn. Do not Repeat Yourself.
It's a good rule. You'll figure it out on your own even if nobody teaches you.
"Make a button"
Okay, you can write a component like this:
const Button = () => <button>Button</button>;
"I want to have a button that says Button and a button that says Click Me"
You got it!
const Button = () => <button>Button</button>;
const ClickMe = () => <button>ClickMe</button>;
"Now I want Button to open the shopping cart and I want ClickMe to close the page."
Okay, we can do that.
const Button = () => <button onclick={openCart}>Button</button>;
const ClickMe = () => <button onclick={closePage}>ClickMe</button>;
"Wonderful. Beautiful buttons. I want a third button, and this one should close the currently open modal."
Ah, this is getting tedious. Are you going to want many more buttons?
"Yeah, maybe?"
Okay. Let me DRY this up.
const GenericButton = ({ onClick, label }) => (
<button onclick={onClick}>{label}</button>
)
const Button = () => (
<genericbutton onclick={openCart} label="Button">
)
const ClickMe = () => (
<genericbutton onclick={closePage} label="ClickMe">
)
const CloseModal = () => (
<genericbutton onclick={closeModal} label="Close Modal">
)
</genericbutton></genericbutton></genericbutton>
"Wonderful. Can we make all those buttons 20px font?"
Yes, of course!
You've just DRY'd up your code, and here's your first victory. Buttons use a common underlying component. Giving them all a big font is easy. You got dis.
const GenericButton = ({ onClick, label }) => (
<button 20="" onclick={onClick} style={{ fontsize: }}>
{label}
</button>
)
All your buttons now have a bigger font. DRY Magic.
A true victory for engineering. You pat yourself on the back for an architecture well designed, code cleaned up. You followed some great engineering principles! 👏
But you just shot yourself in the foot.
Next week, your PM comes back and says, "Button must be green, ClickMe should be blue, and CloseModal is red"
Okay, that's annoying, why 3 different buttons, but you can extend GenericButton
to accept styling. You've still got dis.
const GenericButton = ({ onClick, label, style }) => (
<button onclick={onClick} style={{ fontsize: 20, ...style }}>
{label}
</button>
)
const Button = () => (
<genericbutton onclick={openCart} label="Button" style={{ background: 'green' }}>
)
const ClickMe = () => (
<genericbutton onclick={closePage} label="ClickMe" style={{ background: 'blue' }}>
)
const CloseModal = () => (
<genericbutton onclick={closeModal} label="Close Modal" style={{ background: 'green' }}>
)
</genericbutton></genericbutton></genericbutton>
You had to add styles to every call of GenericButton
, but that's okay. At least you have components so you didn't have to find every single invocation of any button and change them.
That's the power of DRY. Change once, fix everywhere.
Then your PM says "The font on CloseModal is too big"
A-ha! You were smart. You wrote GenericButton
so styles provided through props can overwrite default styles.
{{ fontSize: 20, ...style }}
versus {{ ... style, fontSize: 20 }}
.
"CloseModal and Button should be disabled when they're inactive"
Hmm… 2 of your 3 buttons need new behavior. Best add it to the base implementation.
const GenericButton = ({ onClick, label, active, style }) => (
<button onclick={onClick} disabled style={{ fontsize: 20, ...style }}>
{label}
</button>
);
You change Button
and ClickMe
to set the active
prop based on some state.
And ClickMe
became disabled too? That's weird.
Guess you have to change that one so it's always passing active=true
. That's weird.
Why would a button always proclaim itself as active? Buttons should be active by default, and this feature should exist only for buttons that use it.
You adapt the code.
const GenericButton = ({ onClick, label, active, style }) => {
active = active === undefined ? true : active;
return (
<button onclick={onClick} disabled style={{ fontsize: 20, ...style }}>
{label}
</button>
);
};
Now active
is true
when undefined, and whatever you set otherwise.
The DRY footgun
Your GenericButton
is starting to swell with edge cases. As time passes, it's going to get worse and worse.
You'll add a loading state. But only some buttons will use it. You'll add hover/unhover behavior. But not all buttons want that.
Eventually, you'll have a button with so many features it's impossible to use. Every render requires so many props that you might as well write a whole button from scratch.
Happens a lot.
When it starts having a general purpose. The amount of times I shot myself in the foot by generalizing too early is too damn high
— Swizec Teller (@Swizec) January 4, 2019
YAGNI to the rescue
YAGNI, ya ain't gonna need it, is a programming philosophy that engineers learn a little later in life.
Because it takes a while for those DRY footguns to appear, perhaps. Or because many projects don't live long enough to see it. Maybe it's just a matter of thinking about why your code is suffering.
YAGNI often comes in 2 shapes 👇
The first is like the above. You spotted an opportunity to optimize, but you were too early. You should avoid generalizing code until it is absolutely obvious that you should.
The more use-cases you have, the easier to know what to generalize. I mean, how can you know which functionality your components share if you don't even have those components yet?
You can't.
And that brings us to the other YAGNI: Building stuff you think you'll need before you need it.
You think of a wonderful abstraction. A wonderful little feature that's gonna help oh so much in the future.
You build it.
Months pass. You tweak the code here and there. Nobody's using it, but you have to maintain otherwise it's gonna break. Whenever you implement a feature, you have to think about how it impacts this code you've got.
It's slowing you down. Making you think. And you aren't even using it.
But one day! One glorious day, your PM gives you a task. A task from the gods. You are about to use this code you predicted 6 months ago!
You are a god of prediction! You knew it all along!
You delete your code and start from scratch. The feature is so different that you can't use any of it.
Continue reading about DRY is a footgun, remember to YAGNI
Semantically similar articles hand-picked by GPT-4
- DRY – the common source of bad abstractions
- DRY vs SoC, a difficult choice
- Variants – a quick tip for better React components
- Build a new design system in a couple afternoons
- Why CSS-in-JS is winning, an example
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️