> 64. DRY (don’t repeat yourself) is a luxury for when you have a coherent theory in your mind of the app’s code.
> It’s for when you have that theory contained in a worldview in your mind that helps you quickly test out decisions and plans against that theory.
> Until then, you should repeat yourself. Seeing your code repeat itself – when rhyming phrases start to pop up throughout – is a vital tool for building a theory about your app.
Wow, this is the best explanation of how to apply DRY that I've ever heard.
I've seen so much code being messed up by dogmatically trying to follow "best practice". The worst offender is exchanging state variables for storing the state in the code path.
That's interesting. DRY is one of the few things I'm very insistent about. I don't trust myself to fix a thing in multiple places. I've been bitten by this too often. I trust my colleagues even less, because they might not even know that the code is repeated elsewhere.
The comment and the article doesn't advocate repetition for repetition sake. It serves as a warning against prematurely trying to "factor out" seemingly related code that can, later or in that moment, prove to not be exactly the same. This complicates the "refactored" or "improved" piece of code, as it now has to serve different purposes, maybe requiring more parameters, or a more complex state input. This further complicates the callsites of all the clients of the new DRY piece, adding complexity to the system which is more often than not worse than the original state.
It also makes everything around those places more difficult to change over time.
Since this "refactor" is often easy to spot, it tends to be abused by less experienced developers trying to improve things, or "advocates" of this practice that aren't in touch with how it can end up causing more damage than it fixes.
A DRY refactor has much higher chance to stand the test of time if you know enough about the system and how it will evolve at the time of performing it.
As with most things, it's about striking a balance. Since the internet is full of DRY! refactor! advice, this serves as a counter to the mindless call to DRY by adding some nuance.
But OFC there are instances in which code that is exactly the same is copy pasted around even if it's known one won't diverge from the other and done out of laziness... that'd be the trivial, positive DRY refactor case.
1. you are writing some code, there are a number of functions dealing with similar things and you know how all the stuff relates to each other, you find yourself repeating some code in places and immediately realize you will need to repeat it a lot of places because all these things are related together. So you make a function you call.
2. You have come to a new codebase you don't have familiarity with, you see some bits of code repeated around in various places and you don't know if this is just because the things actually are related to each other or if it is basically by chance. You replace these bits of recurring code with a function. Later on people keep coming to your function and start adding in parameters and branching logic to handle the different use cases that keep popping up in your application where it is being used because there was actually not a very tight logical connection between these places it was being used and as a consequence over time the places it was being used are diverging in their needs.
I won't offer an opinion of my own because I don't have one yet, but I've seen several articles/comments here on HN lately expressing that one should not follow DRY blindly because there are cases where it leads to over complicated code. I remember there was an example showing one such case.
Agree, although with the caveat that if you find yourself deliberately copy-pasting duplicate code, you should consider going DRY immediately. You might later realize the code really should be duplicated (because it is "accidentally alike" rather then "essentially alike"), but it is much easier to turn DRY code into duplicates than going the other way.
I'd argue the exact opposite, it's much easier to take duplicated code and abstract it away. If you find that's not the case, maybe that's precisely because it wasn't such a good abstraction to begin with.
In practice, what I've seen too often is that code that was duplicated but should have been factored out ends up evolving with people having forgotten that there were duplicates. Bug fixes that should have been implemented in all instances of the duplication only end up in a few of them. When you do go to factor that duplication out, you're stuck with 5 to 10 different versions that all have different bug fixes, and theoretically they all need all of them.
On the other hand, if someone finds that one instance of the duplicated code really is different, they either turn it into a new function (the right answer) or add a new argument. Even if they take the wrong path by adding a new argument, it's easier to turn that one function into two functions than merge 5 to 10 different versions of what should have been the same code.
Yeah exactly. If two block of code are literally duplicates, it is easy to consolidate. But in reality they will drift apart over time, making it more difficult and risky.
The problem typically comes down the road when those abstractions touch so many pieces of the code that they become extremely expensive to change. The problem isn’t the hasty abstraction, the problem is the lock-in it creates.
This is a false dichotomy in my opinion. DRYing some code doesn't prevent one seeing how it is used in many places. To the contrary, I would say that DRYing makes it easier to see repetitions, because what you thought was a repetition could be slightly different otherwise.
Code should be kept alive. It is fine to DRY wrong. You can make it better when you discover a better way.
If one writes an AbstractFactoryFactory where a function would suffice, I wouldn't consider it DRY. That would be a premature abstraction. And premature abstraction is the root of all evil. By wrong DRY, I meant a DRY done at the right level of abstraction, but in a way that doesn't address short-term evolution of the code. In such a case, it should be easy to change the abstraction.
Wrong DRY is necessarily a premature abstraction because every act of DRY creates a new abstraction. If the DRY was wrong, the abstraction (new method) it generated is premature.
I should have stated my definitions clearly, because I might be using rather unorthodox definitions of “wrong” DRY and premature abstraction.
Premature abstraction is premature at the time of abstraction. It aims to address future needs that may never happen. Wrong DRY is correct at the time of abstraction. It is wrong only in hindsight when a different need that is not addressed by the DRY arises in future. In that case, it should be easier to change the abstraction than to find duplications and DRY them. DRYing early also allows getting the benefits early until the needs change.
I think of it that way too. You want to make it obvious where the regularities and the differences flow to. It’s a bottom up, gardening kind of programming.
What’s more important IMO is to keep things together and in harmony. As in the same file, or similarly named files, or something that let’s you see it somehow. That’s a structured kind of repetition that is rarely harmful.
The problem with DRY is when you have multiple knobs at different places that you have to turn in sync that are structurally unrelated. Now you need to refactor or redesign to have a clearer pipeline and knowledge representation.
DRY problems can often be symptoms of bad structure. You can only accidentally fix it by patching or abstraction over repetition.
Sometimes I think that choosing identifier names and deciding between copying code and extract common code are hardest problems in programming. There's no right answer, but choosing wrong answer hurts program maintainability.
The problem I have with this approach is that I don't have a counter in my head. I can tell whether I saw it before, but cannot tell at how many different places. Therefore, I find that it's best to DRY at the second instance. Doing so also avoids neglecting to DRY some instances and potential DRYing of them in another way.
Then again, sometimes it's even harder to take the "DRY nothing" out of them once they decide "DRY everything" was wrong but misunderstand why.
I mean, while the explanation above is nice, it's fairly easy for a junior to speed read through it as "DRY is a luxury [...] You should repeat yourself [...]" -ignoring all the parts that sound too complicated or nuanced- and quickly jump to the completely opposite conclusion.
The way I see it, business logic needs this approach, but all API boundaries must be and remain DRY from the get-go. Interface definitions must not define behaviour; they are glue.
My experience is the opposite. Juniors repeat code (andacutla in-life procedures) a whole lot and are either somewhat-uninterested in DRY or lack the skill to not repeat themselves.
> It’s for when you have that theory contained in a worldview in your mind that helps you quickly test out decisions and plans against that theory.
> Until then, you should repeat yourself. Seeing your code repeat itself – when rhyming phrases start to pop up throughout – is a vital tool for building a theory about your app.
Wow, this is the best explanation of how to apply DRY that I've ever heard.