One of the very first things you learn as a developer is that for code to be “good”, it needs to be DRY. It’s almost as though DRY code is some kin...
For further actions, you may consider blocking this person and/or reporting abuse
You make a good point against premature abstraction and against overloading components with too many options. The latter can be avoided by composition rather than repetition. While I don't think, WET should seriously be a best practice, I know many situations where some repetition is fine and where I would rather focus on getting things done than winning a clean-coding award in the first place, even more so as you might not need it (YAGNI) later. But often there is a time to refactor and make the code cleaner, before it gets messy.
Abstraction is helpful when done right!
Avoid premature abstraction, but abstract when repetition patterns become clear and you find yourself writing the same come again for the third time ;-)
my point exactly :)
Rule of 3
Do any situations come to mind that could illustrate composition over DRY or WET?
You mentioned a modal component that is used to display various types of notifications. Instead of adding more parameters to one common component, you could have different components that use this one combined with other components / interfaces like buttons, groups of buttons, style wrappers etc.
I found Alexi Taylor's article: React Component Patterns worth reading. It explains composition patterns using practical examples in ReactJS.
Right, I think "modular" is the key here. Can we set up the component in such a way, that we can expand on it if needed, without breaking anything else. Atomic design could be of great benefit here.
Example:
._base_popuptitle{
// base styles
}
._variant1_popuptitle{
@extends ._base_popuptitle;
}
You could extend it any way needed. Be thoughtfull though, to not be too specific, but stay somewhat generic.
This can be applied then for each contentsection you have or add later, without breaking stuff.
It's something you need to get the hang off, but it can help greatly.
A function (or... "method", if you will) should
Of course, those two simple guidelines are both somewhat subjective. If you set two variables in your function, is it no longer doing "one thing"? (Most certainly not - but you get my point.) And whether the function is doing its thing "well" is always debatable.
But any time you start abstracting the heck outta stuff, you're almost never doing it well. And we often reach for abstraction when we're trying to make a single function do many different things.
So if you have one block of code that's become a true utility function - the sorta thing you write once and use all over the place, then yeah, of course you should centralize that code. But when you start trying to turn that utility into a Swiss Army Knife - the kinda "utility" that will serve every single alert, that meets every single use case, in every single scenario - then you're probably creating an abomination.
I think that thought is a bit misleading: functions serve as an interface between two levels of abstraction and the "do one thing" rule should apply mostly on the level of the caller, not the internals of the function.
A function (or any other type of subroutine, really) might do lots of things internally that present present as a single atomic action to the outside.
Also, there's one statement that can be generalized a bit:
See? Much more DRY now 😁
Software Development By Slogan usually does.
Don't Repeat Yourself is a slogan that refers to:
When phrased that way, "duplication" has a lot more nuance.
The Wrong Abstraction
YouTube
I really like that phrasing. Thanks for sharing that article :D
I'll also use the chance to throw in a link to one of my favourite programming books: 97 things every programmer should know, thing 7: beware the share
That particular piece is by Udi Dahan, Founder & CEO, Particular Software. The talks given by his employees can be interesting as well.
In particular finding your service boundaries comes to the conclusion that startups have difficulties identifying boundaries because their business rules aren't stable - which means that μservices are not a good fit in that situation.
And it's in bounded contexts (which is what microservices are all about) where naive application of DRY (or overzealous pursuit of reuse) can be counterproductive. Note that both the sales context and the support context have their own version of
Customer
andProduct
.While sharing the same name these entities will likely have a very different shape inside each context as they support very different roles. Unifying them into one single entity would only increase coupling, undermining the autonomy of each context. Instead representations of
Customer
andProduct
that are shared between contexts are minimal - just enough information to correlate the support version with the sales version.I agree with the sentiment that devs tend to over-abstract, especially newer devs. The more experienced you get and the more you understand your domain, the easier it is to know what abstractions you need and will be useful.
To your point about the single modal function, we have a rule of thumb. If you find yourself "configuring a function" (passing string parameters like "alert" is a good indicator) then you're abstracting wrong. It's not necessarily that it shouldn't be abstracted, but it's much better to have an alert modal as a separate function from a form modal function, perhaps with some shared underlying function that wires up a generic modal.
I think good advice for newer devs is to lean more toward WET but consult with more senior devs if you think there's an opportunity for abstraction. You will learn a lot about the domain, the architecture, and how they think during those discussions.
I agree that anticipating factoring too much can lead to bloat. I think some of us also just have bias for the puzzle part of refactoring things 😝
That being said, refactoring (thus DRYing), is better done early. DRYing a project once it's done and/or you forget about everything, will lead to debugging hell. There a reason why people have gone mad on DRY code. But it's all down to common sense and the fact DRY is a tool, not a goal. Clean and readable code is.
In FP, I really don't mind having a few small functions that are only slightly different and easy to duplicate if you need a new one. A simple cmd+D might be enough to update all of them. In OOP, I don't mind having the same method on multiple classes, because I might want to explicitely change each of them, since they refer to different objects.
Really, if you focus on your code being elegant and readable, DRY will be one of your best tool, almost without thinking about it. Over DRYing should pretty easy to avoid—and quite rare, since it is a pretty painful task in the first place.
In the case you described, I fully agree. The concept of YAGNI should be always on top of developers' minds whenever they write code, but going WET by default doesn't solve bad thinking or premature abstractions.
+1
My thinking is that if we can do stupid things, we're going to. So we need systems in place that help us avoid that. If my norm is WET, then for the third usage I have to sit down and think what is actually shared - I have a better chance of avoiding premature abstractions.
I really don't think that PopUp you are describing was DRY at all... rather a huge mess with everything in one single place. ( I also worked in a company where we had a very similar pop up. very very similar. hundreds if not 1000+ LOC with incredible cyclomatic complexity due to all those flags and conditions. working and debugging that was pure hell.
But again, that was far away from being DRY, and anything but SOLID.
I absolutely agree that premature abstraction is wrong. Most of the time YAGNI, so why bother. but as soon, as you said, you realize you wrote more than TWICE, you should do something about that. which is NOT a God Component like the one you described. rather a more functional approach, with tiny components or methods that can be composed, chained together, decorated and adapted, and built according to your need using different design patterns.
Great article!
I especially prefer "WET" code in unit tests, where constantly trying to cut lines by consolidating similar tests using "helpers" can result in giant methods with many flags.
The original goal was to make maintaining the unit tests easy, but now the helpers are so complex so that adding new test paths takes hours.
Personally i believe in DRY code and i try as much as possible to stick to it, it comes with a few hitches because the more complex the problem or the solution is the harder it is to get the abstraction layer right, but in the end if you spend some time thinking about the problem or your solution to it, you will find recursive patterns that can help you get your abstraction right. WET code gives you more work than you should have to deal with, imagine having to update over 50 lines of the same code in like 5 components?? At the ending of the day, even if you find dry code quite complicated at first, ask the guys or yourself about the problem, then loom more closely at the solution.
" 50 lines of the same code in like 5 components"
if it appears 5 times, then you should look really hard into abstracting it. But if only appears twice, it may be too early to tell.
I agree it's important to write something at least twice or three times before trying to abstract or generalize it. Also, if the abstraction makes the code less clear, don't do it. YAGNI the abstraction if you only write the thing a few times.
The thing with DRY code is, you need to look at your abstractions from further away than just the code level.
If a chunk of code does a very well defined thing, something that's a reasonable action within the problem domain? Then it should be abstracted into some form of construct (be that a class, function, whatever). Think of methods like cancelling an order; this will likely happen at more than one place and is a distinct process even if you set aside the code.
Something like a popup is a bit tricky; when the code is well written, there's nothing inherently wrong with a single modal class that takes a bunch of callbacks and has a bunch of different variants. In OOP these would normally be separated out into subclasses, but in languages like C it's also not that strange to simply use flags for such behaviour.
The problem really starts when the code structure is bad and the class turns into an entangled mess.
Generally speaking, DRY code is better than WET code (which I prefer to read as "We Enjoy Typing"), because copy-paste errors are a very real threat and keeping two different sections of code in sync is another pitfall that may introduce bugs when one of these sections is forgotten.
Another problem that often gets overlooked with DRY code is that some times two sections of code just happen to follow the same logic, but don't really relate to each other in any way. Abstracting this common code out into a separate construct might turn out to be a terrible mistake if one of these sections ends up changing while the other doesn't: now you have to roll the abstraction back or bloat your abstraction.
Always be aware of which code is shared incidentally and which code share reflects actual shared domain logic.
Dan Abramov has a blog post about it too: The WET Codebase
I think there's a few problems with WET. Firstly, when you copy, you may not realize whether you're copying the first, or the copy of a copy of a copy because the names will all be different. So advocating for a little wet is advocating for a viral bloat-iness. The find-all-the methods problem is compounded because these code bases naturally are bigger than their DRY counterparts. I've personally been bit by this. As a newbie to a particular codebase, I implemented the exact feature modification asked for by the Product Owner with tests, etc. Only to find out it "didn't work" for the customer, which was because their path was triggering the "wrote it twice" copy of the implementation in the giant, bloated file with duplicate implementations everywhere. Hard pass on WET from me.
I've heard the acronym in this context:
DRY • Don't Repeat Yourself
for code, but...
WET • Write Expressive Tests
for unit tests.
Meaning that unit tests should be independent of one another, self-contained, and not entangled with other tests, or global state, or unit test order dependent (i.e., the finishing state of one unit test is presupposed as the starting state of a different unit test, so they are implicitly conjoined).
Some unit test frameworks provide a convenient setup / teardown used for all the bundled unit tests... even that is suspect (but not slavishly disallowed) for WET, because the tests are now entangled with the setup and teardown, making them harder to maintain.
As far as "write everything twice"...
My tipping point for refactoring something used to be "third times the charm". If I had the same logic in three or more places in the code, I'd take some time to refactor that to a single helper routine.
I changed my mind later to more faithful DRY, when the same logic in two different parts of the code (not my code, but the same sentiment) had the same bug, and another developer fixed the bug in one of the two duplicate parts of the code.
The unfixed bug in the other code path probably cost the company upwards of ten million dollars.
An expensive lesson on someone else's dime.
I found this post to compare 3.
DRY, WET, OR AHA?
Boluwatife Fakorede ・ Mar 18 ・ 3 min read
Yes, WET is good when T means Twice :) If more than we need to abstract. Early abstraction hurts more than it does good.
Exactly