In my mind, the point of abstraction is to transform models such that we can build solutions in a way that is a better fit for the problems at hand. Reducing LOC and repetition is explicitly not the goal, sometimes a good abstraction may actually result in more lines of code (but more often it's less). So reducing LOC and repetition is commonly a happy byproduct, rather the reason we do it in the first place.
I see slightly more experienced programmers rebound from DRY to a point of having a pathological distaste for abstractions, and I find those codebases to be far more stressful to work in than ones which just happen to have a few bad abstractions.
Of course, the copied code was often buggy, and they would only fix the instance in the controller where the bug was reported. When bugs were fixed it was pretty much random chance which version would be chosen to copy when needed again, so creating the right abstractions involved piecing together an entire phylogenetic tree of the code to decide which pieces were supposed to be the same and which were legitimately different.
Add a commit history that looked like "v1", "v2", "v3", and... yeah. We finally decided it would be cheaper to scrap the app and rewrite it from scratch.
So, yes, premature abstraction is a huge problem, but so is the opposite extreme, and after that experience I personally would choose overly-indirect code over the mess I inherited. It's easier to untangle a function that has too many callers than it is to find code that should have been shared after months of divergent evolution.
It's enough pain when the effort is too big to avoid (e.g. Js frontend, Rust Backend) it.
Juniors will learn the difference between actual and imagined similarities at some time.
And thank god for github actions! :D
I'm pretty sure all of the most fundamental principles of good code are in tension with each other.
The problem with DRY is when abstractions with currently identical implementations are given a single interface, even though they're logically distinct.
Then, when those distinct abstractions' implementations need to diverge, you've got a rats nest of references to manually pick through and separate out.
Or worse yet , the mistakenly-shared interface becomes parameterized, leading to a horrible mixing of requirements and concepts that may never be untangled if the original intent is lost to time.
Obviously DRY is great when you can consolidate multiple implementations that are actually a single abstraction, but it can really go off the rails if the motivation for the mantra isn't understood.
Then your tester says it isn't correct in 2/3 places, you find out you've only updated the function in 1 place and either abstract those the other 2 cases out or update it in those 2 places aswell.
[1] https://www.youtube.com/watch?v=0FIZn2trkoA&pp=ygUXbWljaGVsI...
The best was KINDergartener (uses all the letters). I would describe this as a programming paradigm for people who are new to the programming and real work and havn't found out that the getting shit done and shipping the code to users is the most important state for code to be in.
Then program with the things you have learned in mind, instead of blindly following the some "paradigms rules", 'cause that's never ending well.
But if you hate DRY so much, imagine a word where people set it to 1 or 2. I wouldn’t want to work in that code base.
I apologize for my dry humor.
I remember a ex-colleague of mine who copied a 1000 line function, changed a parameter, and didn't see anything wrong with it.
Hey, rule of 3 ;)
Anyways, guidelines are just guidelines, regardless of what you come up with you can break it.
Often times it seems like we aim for DRY at the expense of simple, or idiomatic code. It also has a nasty habit of making code difficult to change since it leads to a lot of premature coupling, where someone sees a repetition and naively assumes that that needs to be eliminated, when it might literally be a naturally independent value.
DRY, when used with functional, well named code is the number one thing keeping a codebase easy to read. There should be one way to achieve something well-defined like save/edit/delete an entity, check a file type, url encode a string, etc.
This ALWAYS leads to much easier code fixes AND much easier refactoring.