DRY is a footgun, remember to YAGNI
swizec.com
swizec.com
Sometimes that means centralising code so that change can be limited to one location. Sometimes that means creating strong seams between areas so that changes stop rippling out at the boundaries. Sometimes that means allowing duplication strategically so that your inadequately informed preconceived notions about what future change will look like don't precommit you to unnecessary change.
YAGNI and DRY are great, but the fundamental principle underlying them both is the containment of future change.
Uff...that's a heavy statement. There is no such hard and fast rule. In fact its mostly the opposite, I argue. 90% of the abstractions don't ever get used. Write twice and then make an abstraction.
I think you're doing software engineering the wrong way, sorry to say.
I think you mean YAGNI, and I agree. I've seen plenty of over-engineered solutions.
On the other hand, when the parent say "future change is contained," I take that to mean separation of concerns, plugin-based architectures, decoupling of business logic, etc. And I can also say I've seen plenty of under-engineered solutions, where you need to change constants in five places, and twenty different if-blocks just to add a simple feature.
An experienced developer who's been using the codebase a long time can sometimes spot when an abstraction is needed right away. Conversely, an inexperienced developer won't write twice and make an abstraction, they'll write five times and make a headache for everyone.
Coding to avoid or contain change does not mean overabstracting. Overabstracting generally means more change down the line, which is one of the major reasons its bad.
So software engineering is really the art of learning how to properly create DRY code. We should focus more on how to better create DRY code and not talk about the few times where the abstractions we created didn’t work out.
We need abstractions to survive as an industry.
Actually for is a great example of why DRY is necessary. Do you implement your own iteration mechanism every time, or do you use the provided tool (for) that your language has generalized for you?
Then, it’s very difficult to give general advice because the usefulness of an abstraction is completely contextual. But I remove duplication from the start, and I re-duplicate by replacing the abstraction with its code inline in the places it isn’t working any more. That allows me to look for and apply a new abstraction.
I started adopting this a few years ago and it’s surprising how seldom I hit three identical, non-trivial code repeats. Saved a lot of hours over the years not making a seemingly prudent generic layer.
"Next week, your PM comes back and says..." "then your PM says..."
you cannot abstract in code somebody's opinion, at least not other people's. the number of possibilities is too high, and you have no clue what they are. this is why your carefully crafted structure will break down -- it has no basis in reality.
sidenote: if you will call yourself an engineer than you need to think on a different level than implementing somebody's whims. but of course a lot of of this type of work is fiddling with things to make somebody happy. I would think a real engineer would provide a technical solution to a non-technical person for them to fiddle with themselves.
Fiddling is one thing, and designing a system to enable fiddling is something engineers often do. But a lot of PM requests aren't even close to fiddling. If your product is a streaming video service, adding DVR capabilities is something a PM might request. No amount of non-technical fiddling will enable that functionality. But a well-designed architecture with isolated functional components will make the work of adding such a feature far less painful than a poorly designed one would.
This is spot on. And also, DRY is for concepts and abstractions, not just duplicated lines of code. Picking the wrong abstractions often has a much higher cost down the line than some duplicated code.
Why? You couldn't have used a default style that could be optionally overridden?
> Your GenericButton is starting to swell with edge cases. As time passes, it's going to get worse and worse.
Sure, if you never split these things out into other modules. A bit of composability would fix this.
----
Like, this whole article seems to misinterpret DRY to mean "shove everything physically possible into a single implementation", when nothing can be further from the truth. YAGNI and DRY are not alternatives to one another; they're complements to one another, and both are essential for writing clean, readable, extensible, and maintainable code.
For example (provided the language or framework supports this), you could split the styling functionality of a GenericButton off to some separate function (say withStyle) that merges style rules into an element's existing style, and thus instead of having to "change once, fix everywhere" you'd just invoke that function on the ones where you want to override a style. And indeed, if you're using a specific style change frequently, you could split it into its own specific function and reuse it there.
And then - here's the best part - chances are there are things other than buttons that could use styles, so if you implemented withStyle (and derived functions) right - tada! - it's automatically usable with those other things. Wanna style a paragraph? Feed it into withStyle. Wanna style a text input? Feed it into withStyle. Wanna style a picture? Feed it into withStyle.
My experience is that junior programmers tend to error on the side of writing lots of repetitive, boilerplate code and they would produce better code by being more willing to write reusable abstractions. It's the mid-level programmers that err on the side of creating grandiose frameworks with unnecessary abstractions.
Personally I like DRY, I like WET, I like YAGNI. I see them all as tools in my toolbox as a programmer and I find the art is to know when to use which tool. In practice, my code never has the perfect amount of abstraction vs. repetition vs. generalization but I think I keep them in a manageable range and refactor when things start to feel like they're going off the rails.
I'll just add that thinking about future use is a practice inherited from classical engineering (the one without "software"), where you had to plan for the potential future installment of cabling or pipes, before it becomes prohibitively expensive to tear walls down and start anew. No YAGNI ain't gonna save your 19th century self.
If course, this practice doesn't make as much sense to the fast paced start up world, where time to market is more important, RAM is cheap, and your company's going bankrupt in 6 months.
IIRC, that section (maybe the same page) was also talking about loop unrolling and counting the number of cycles used in various situations.
Not big O, literally the cycle count in the MIX architecture.
Donal Knuth said this when talking about programmers decades ago trying to noodle loops and expressions while they were writing them. In the modern era the same optimizations aren't even necessary at all, because the small stuff is done by the compiler.
People think it is a good plan to throw caution to the wind and figure out speed later, which just isn't true. You have to architect for throughput, latency and interactivity from the start and language does matter.
If you write your code right then refactoring it to use a different more whatever algorithm should be if not easy at least 'not hard'.
Could you?
It's like an hour to copy/paste a reference implementation and set up unit tests. Maybe an hour or two fine tuning the implementation for your language and benchmarking some use cases.
I'd been using the same C# Boyer-Moore impl for like 15 years, and happened to have just recently updated it for Span support. I doubt I have 8 hours into it in total, and it's thoroughly benchmarked and tested.