8 ms·
> An early abstraction that would've grouped those coincidentally similar pieces of code would then have to stretch to cover both evolutions. This seems to be
by mostlylurks 3y ago
> An early abstraction that would've grouped those coincidentally similar pieces of code would then have to stretch to cover both evolutions.
This seems to be the underlying assumption behind most uses of the "duplication is cheaper than the wrong abstraction" quote, but the assumption is simply incorrect. You should almost never try to expand abstractions in this manner. If you don't treat the abstractions relating to the thing you want to change in your codebase as "the" place where you need to make your change, and instead eagerly make new abstractions and throw old ones away as required, you won't really run into this problem.
In fact, this predominant mindset where creating abstractions is strongly discouraged leads to the very problem that mindset is based on, as it will simply encourage junior developers and the like to modify the existing abstraction, creating the aforementioned kind of mess where abstractions become complicated through repeated modification, instead of creating new abstractions when appropriate, because creating abstractions has a stigma attached to it.
Additionally, if someone has made a "wrong" abstraction based on something silly like two pieces of code simply being similar in terms of their structure and those use cases start to drift apart, you should feel eager to simply split apart the abstraction, be it into bare implementations or two new abstractions, or any other combination. Abstractions are cheap as long as you don't give them special significance.
- sweezyjeezy 3y agoI think there's a middle ground here. The original quote does not mean DRY=bad, abstraction=bad. The point is there is a non-zero cost to these things. A bad abstraction can, as you say, accumulate to something terrible through inertia or inexperience. A bad abstraction, even if caught early, was probably not worthwhile - I mean, it took time just to make the original one right? This does not mean that we should be scared of abstraction in general, but in my opinion abstractions that are purely for the sake of reducing duplication should be viewed with an extra level of apprehension.
- jameshart 3y agoWhen an abstraction evolves to a point where it needs to be split into two separate implementations to meet diverging needs… you will need to replace that abstraction with duplication. Which is the right thing to do because that duplication is cheaper than maintaining the wrong abstraction. I think this post makes the mistake of thinking that the only way in which duplication comes up is that it is discovered in the codebase, and we have the choice of abstracting it away or keeping it. On the contrary, duplication can - and should - be consciously introduced to fix bad abstractions when we find them in the codebase.
- cratermoon 3y ago> When an abstraction evolves to a point where it needs to be split into two separate implementations to meet diverging needs… you will need to replace that abstraction with duplication. Hard disagree. When the formerly common parts of an abstraction evolve to no longer be common, then that duplication no longer exists. There now exists two abstractions, one for each of the diverging needs. There may be some leftover commonality that can be abstracted out, but it's no longer the original abstraction.
- hannasanarion 3y agoThe point is that they were never actually common in the first place, only superficially similar. You're saying we should look for duplications, abstract them, and then every time a change needs to be made to the abstraction to suit only one of the use cases, refactor the codebase to de-abstract and re-duplicate, undoing the work we did in the name of DRY in the first place. That is a lot more work and a lot more confusion and a lot more headache for maintainers and reviewers than copy-pasting the thing the first time, having realized that the duplication was incidental, not structural. Let's take this line of reasoning to its extreme: I notice that there's a section of my code that's repeated twice where we add one to a value, so I abstract it into a function called add1(x:int). Some time later, at places where add1 is used we sometimes need to actually add a value other than one, so we need to make a decision: do we refactor everything and re-duplicate, or do we stick the DRY principle and make our abstraction more accomodating? The path of least resistance is to stick to DRY because it's a smaller and more comprehensible commit, so we add an optional arg, add1(x: int, operand?: int). Some time later one of the callers to this function needs to pass a vector instead of a single value, so we need our add1 function to have polymorphism and conditional logic in it now, and potentially more arguments. Sooner or later we have a frankenfunction that's hundreds of lines long and branches a bazillion ways and might as well be a turing machine in itself. Dogmatic adherence to DRY leads to madness.
- cratermoon 3y ago> You're saying ... refactor the codebase to de-abstract and re-duplicate, undoing the work we did in the name of DRY in the first place. That's the exact opposite of what I'm advocating for, but perhaps I didn't express myself well. > Sooner or later we have a frankenfunction that's hundreds of lines long and branches a bazillion ways and might as well be a turing machine in itself. Yeah, that's not a good abstraction, and not at all what I meant.
- TOGoS 3y ago> You should almost never try to expand abstractions in this manner But somebody on your team /will/. > you should feel eager to simply split apart the abstraction Sure, but it's going to be a lot more work at this point than if we had avoided the mess in the first place.