I've seen this mistake many times, and I've made it myself. When you first read about the DRY programming concept, you probably misunderstood it.
What was going on in your head was this:
Wikipedia: DRY stands for not repeating the same code twice.
You: Hmm, ok I'll replace all my duplications with abstraction.
And it seems like a good solution, but it's not. Your abstraction is often wrong.
Here is why:
- You see duplication.
- You extract duplication into a new abstraction (method, class).
- You replace the duplication with the new abstraction.
- You think your code is perfect.
- Time passes.
- The product manager has new requirements. Your abstraction is almost perfect for them.
- You start to implement the new requirements.
- And here's the little but:
Your abstraction is almost perfect. Why? The new requirements affect only 95% of the old code you extracted into abstraction. The other 5% is not affected. And instead of creating a new abstraction with 95% copied code from the current one, you decide to change the code of your abstraction.
You add a conditional statement,
if..else
for example, and pass a parameter, so your abstraction can perform different actions for different decisions. - Now your abstraction behaves differently for different cases.
- Another new requirement arrives. Another additional parameter. Another new conditional. (Loop until code becomes very difficult to understand and maintain.)
- Congrats, you've created a wrong abstraction.
The code no longer represents a single, common abstraction. It becomes a condition-laden procedure. It's hard to understand and easy to break. Adding new features is incredibly hard and every new feature complicates the code even more.
It's an infinite loop.
So what to do?
Write Everything Twice.
WET
WET (Write Everything Twice) is the opposite concept to DRY. When you start to develop a new system you don't know all the future requirements. So don't rush into abstractions.
You need to remember: Duplication is far cheaper than the wrong abstraction.
For example, you are building a web app and don't have a UI design for all pages right now. But you also have many buttons, and they're similar on every page. You decide to move them to a component called Button
and reuse it on every page. Seems logical.
"Hey, hey!" the new page design came from the product manager. You look at it and find a new, fancy button at the bottom of the page.
It looks like the old buttons, but it has this "one little thing." To implement it, you need to rewrite 10% of your Button
component, and add conditional statements and new parameters.
Now you have a dilemma:
- Change
Button
. Rewrite 10% of the abstraction code (add logical conditions to support new fancy button logic). - Create two abstractions:
FancyButton
andButton
. Copy 90% code fromButton
toFancyButton
.
I know you want to choose the first option. You think that you can handle it. You're not going to build the wrong abstraction.
But the sad truth is, you will (except if you are an experienced programmer and know what you're doing).
Copy that code. Don't be afraid.
After a while, you will know what your buttons will look like in the future. Then you can analyze the current codebase, find duplicated code in the button components, and move them to the good abstraction.
If It's Too Late
If you find that it's too late to deal with a wrong abstraction, the fastest way to move forward is back.
Do the following:
- Move the abstracted code back.
- Delete the unused parameter that's passed to the abstraction to perform different actions for different decisions.
- Delete unused logic.
This removes the abstraction and conditional statements for each caller.
In the end…
If you find yourself passing parameters and adding conditional statements through shared code, your abstraction is wrong.
Prefer duplication over the wrong abstraction.
🔴 If you like this article share it with your friends and follow me on Twitter
🔴 Get more coding tips, job interview advice, and the latest tech news 👉 Join my Newsletter
Top comments (39)
The critical insight is that repetition isn't repetition when two things that mean different things happen to look the same.
In this case DRY becomes the process of introducing fragile dependencies that will break tomorrow.
People say abstraction needs to see duplication, I would say abstraction needs to see how the dublication changes, as if the separated parts change together probably they can be considered as a one thing, if not it's not duplication anymore.
I really hope WET isn't really preached...
If you're having problems with DRY, it's probably because of a violation of something else; SOLID perhaps.
It is 'preached', because the WET approach is what came up when people started getting into problems with DRY. Just like OOP came up when people started to get into problems with procedural code, and that procedural coding came up when people got into problems with unstructured code. These solutions themselves aren't perfect, but they improve upon their predecessor, based on hard earned practical experience.
Just so, @alainvanhout .
And what if you're having problems with SOLID? How far down this rabbit hole do you go to assign blame and find the violation?
And how long do you spend on that (rather than writing code that works, having something actually completed that you can step back and refactor)?
Should you learn and adhere to these principles as best as possible? Absolutely. Should they get in the way of getting actual work done? Never.
I often give teams a similar appraisal of PM methodologies: "They're nothing but sets of tools. Use the ones that make your job easier, and don't obsess over those which do not. If you're following a methodology like religious dogma, then you've missed the point entirely."
Indeed. When dealing with best practices, I try to always come back to and emphasize the 'why' behind them. If the why doesn't (fully) apply, then there's a good chance the best practice also doesn't (fully) apply. Usually both do apply, but not always.
Why? Because it's a BEST PRACTICE™. 🙃
I know there's a perfectly relevant YouTube spoof of best practices out there, but I can't remember who posted it.
I would love to see that video. Any hint on how to find it would be much appreciated 🙂.
Not purely spoof, but I think Ben Awad's video is what I was thinking of.
Yes, the example violates the single responsibility principle. If some code abstraction is doing some branching depending on some boolean flag, it's the first sign of a single responsibility principle violation.
So in this case, when you've got new requirements and understood that you need to do different things, just split the code up for different entities (components, whatever you call them). Each should do just a single thing.
Branching should be done outside of those abstractions, and should be clear and explicit.
Otherwise, later, when you read the code, you will need to go deep down to the abstractions level just ot understand that the flag
A
causes N branching cases.If you keep abstracting the code out then you will have a bunch of very small methods. The new definition of 'fancy button' or whatever can inherit from 'button' and override the bit that is different.
If you have a third level branch with a certain feature, and then you need that same feature in a very distant other 4 level branch, then inheritance will not save you. There's a reason why the main lessen of OOP design patterns (the gang of four book in particular) is ' favour composition over inheritance'.
Not to mention, how much could you have gotten done (duplicating early if necessary) for multiple features, rather than "keep extracting the code out" for one (while guessing at all of your abstractions' possible use/edge cases through some sort of crystal ball)?
My general rule for deduplicatimg is that 2 times is okay and three can be. Every rule for style and organization must be applied with knowledgeable consideration.
Nice advice, I like this!
Premature optimization/abstraction can be evil 😈
I tend to think this way, I usually wait until I have quite a bit of duplication before abstracting or removing it. And even if I have many duplications I wait a bit before and gather all information I can regarding that code, so I can properly abstract it.
After many years of programing, I sometimes know when something will be duplicated and should be abstracted from the beginning. And when and why to apply Design patterns, which if done well can help you reduce the amount of dupped code you write.
But it's always worth to wait a bit before abstracting and try to understand the reason. If it's only to reduce duplicate code you're thinking incorrectly IMO.
"Make it work, make it right, make it fast" sums up the optimization priority pretty well. Just get it working, and then go back and clean things up.
"If I had time I would have written you a shorter letter" -Mark Twain (or Pascal, who knows who actually said this)
I agree, with one "but" (big but): The value of a software product usually hinges on all three of these criteria. The chain thus is only as strong as its weakest link.
If you make it work but fail to get it right or fast, the product holds only little or short-lived value.
Definitely agreed. I believe the point that this mnemonic attempts to get across is not "don't make it right/fast", but rather to shift ones priority focus to getting a working solution first. Then, by all means, make certain that the program is correct, and then please, oh please, make sure it does not run in exponential time.
Very well said. The quoted version alone just makes me think of all the code where people stopped after the "make it work" phase. 😂😭
Nice write-up Nick.
Some more tips:
Terms:
cyclomatic complexity:
a measure for the complexity of code
related to the number of ways there are to traverse a piece of code
this determines the minimum number of inputs you need to test all ways to execute the program
It's gold. It's way easier to abstract when you can see all duplications and compare them.
Nice article!
DRY is just a principle that competes with other principles. Repetition can happen on any level of abstraction, so you need to find a remedy applicable to the right level.
The problem here is not the abstraction itself. Any abstraction only holds for so long and then gets in the way of the implementation. The problem here is that adding new logic usually involves refactoring. With the right refactorings in place, new abstractions can replace old ones.
Besides WET, you could also use different approaches, make different trade offs, such as Composition over Inheritance.
The DRY principle comes from the Pragmatic Programmer. And this book says: it's not about code duplication, but knowledge duplication, which is seriously different. Like very different. Very much.
Like any principle, DRY should be applied depending on a context. Like SOLID, or whatever else you venerate these days.
(I wrote an article about that).
Oh, and by the way, when you use a quote without saying it's a quote ("duplication is far cheaper than the wrong abstraction"), it's nice to say who said that before you did.
If your abstraction doesnt work its not the end of the world. Fix the abstraction, learn from your mistakes and you wont make the same issue twice and the code will be better for next time. That is how you grow.
This approach is the opposite. "We dont know what will change so we will hardcode everything" when you do this enough times the code becomes almost impossible to change fast because of so many little nuances of which you dont know because they are in a separate copied logic. This makes your code buggy and even crashing your application which makes you look bad and your company look bad to the client.
Abstractions help in one MAJOR thing, keeping the same logic in one place. And this is incredibly important because you WILL forget that the same logic is copied across 5 different files causing you to fix something in 3 files but miss two or miss 3 because you didn't know your coworker copied the logic somewhere else. Abstractions can be fixed and improved over time to make changes faster... duplicated code and logic just gets worse over time making your client lose faith and your company not seeing you as anything other than a simple coder.
Now with that out of the way. There ARE some times when its beneficial to do the same things 2-3 times but until you get to the level to understand why, you should avoid it. You will know when you get enough experience in architecture design. There are some abstractions which feels so out of place to join (and through experience you know that caused problems) through inheritance that you know that they should be separated out in duplicated code. But these instances are VERY RARE. Basically, you will know it when you see it.
I had never heard the WET acronym before, I like it :D
It's definitely a balancing act.
WET • Write Everything Twice
...hmm, in my circles it means something different.
WET • Write Expressive Tests
And that means that your unit tests should not be dependent on other tests, or as the starting state from a previous tests finishing state, or having a bunch of shared set-up or shared tear-down helper routines. Each unit test should be self-contained, independent, and stand-alone from other unit tests, and follow the arrange - act - assert pattern, and not have any branches or loops. (I'm using unit tests in the TDD-sense.)
I don't know. My experience is that I just write it until the feature I'm building works (and passes whatever tests). Once its completed, I can then reiterate the code to make it DRY again. I'm looking at it like building cement blocks. It needs to be wet to put into a form before it becomes dry. Although I would prefer to use dry cement blocks right away as it saves time from further reiteration.
As I like to say, duplicated code is cheaper than bad abstractions (what happens when you go bone dry)
I like Kent Dodds AHA Avoid Hasty Abstractions
kentcdodds.com/blog/aha-programming
Thank you. I couldn't remember the acronym Dodds used, though I just read (reread?) that recently.
Good article. I'd suggest adding examples for context.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.