In the industry code that isn't DRY is a much bigger problem than code that is too DRY.
In the industry code that isn't DRY is a much bigger problem than code that is too DRY.
For every purported best practice, there are teams/orgs that painted themselves into a corner by getting carried away and others that really would have benefited from applying it more than they did.
In the case of DRY, it's an especially accessible best practice for inexperienced developers and the project leads many of them become. Many many teams do get carried away, mistaking "these two blocks of code have the same characters in the same sequence" with "these two delicate blocks of code are doing the same thing and will likely continue to do so"
Having advice articles floating around on both sides of practices like this helps developers and teams find the guidance that will get them from where they are to where they need to be.
Context, nuance, etc, etc
Best practices can be like that. "Here's something to avoid!" "OK, I'll back far away from that." Yeah, but there's another cliff behind you, of the opposite error that is also waiting to wreck your code base.
Listen to best practices. Don't apply them dogmatically, or without good judgment.
I absolutely agree with this, and the only thing I would add is that is difference is even more pronounced in codebases using a dynamic language.
Sure it's not easy to navigate a bowl of duplicated spaghetti, but navigating opaque DRY service classes without explicit types is a nightmare.
Luckily as an industry we've realized the benefits of static typing, but your point still holds true there.
Good way to go at it is to isolate the functionality that is used many times and to pull it aside in its own function (or similar). That's just good code practice and also makes it easy to refactor and modify as needed.
Shareable components are more effort to maintain, so just creating them because they consolidate code is not always a good idea. You really want to have positive ROI here and you only get that if you actually reduce maintenance burden. For raw code duplication that doesn’t have a maintenance issue on it‘s own, the bar is a lot higher than most people think.
It would have been OK if they'd used the right methods on Buffer but they didn't.
These encoding/decoding methods are a very good example of code that should be centralized, not least so you can write tests for them. (It is a favorable case for testing because the inputs and outputs are well defined and there are no questions of whether execution is done like you might encounter testing a React component) It is so easy to screw this kind of thing up in a gross way or an a subtle way (I'm pretty sure btoa's weirdness doesn't affect my application because codepoints > 255 never show up... I think)
There's the meme that you should wait until something used 3 times before you copy it but here is a case where two repetitions were too many and it had a clear impact on customers.
To get internet points easily, write something of that:
“Clean code is overrated”
“SOLID is holding you back”
“Tests are less important than profits”
“KISS is the only important principle”
“Declarative programming is only suitable for pet projects”
“Borrow checker is the plague of Rust”
and so on.
Huh. When you put it that way, that's actually a good point. In my experience, competent programming will try to consolidate repeated code, and then cite "because DRY" if asked why, but I can't think of any case where I or anyone else competent started with "needs more DRY" as the original motivation (as opposed to "this is a incomprehensibly verbose mess" or the like).
Conversely, starting with "don't repeat yourself [and don't let anything else repeat itself]" as a design goal does seem to correlate well with cases where someone temporarily (newbie) or permanently (moron/ideologue) incompentent followed that design principle off a cliff.
My experience is the opposite. The less code, the better. I just spent a week on refactoring UI automation test code where they had copied the same 30 lines of code into almost 100 places. Every time with an ID changed and some slightly different formatting. It took me a few days to figure out that these sections do the same thing so I decided to introduce a function with ID as parameter. It was a lot of work to identify all sections and then to make sure they are really equivalent.
Saved us 3000 lines of code and now we can be sure that timeouts and other stuff is handled correctly everywhere. An we can respond to changes quickly.
that's DRY to me. Don't copy/paste code. Introduce functions. Ideally in the simplest way. When you have functions, you declare the same behavior everywhere.
As with anything dogmatic, it truly depends. There are times when the abstraction cost isn't worth it for a few semi-duplicate implementations you want to combine into a single every-edge-case function/method.
One of my biggest gripes (related to the post) is the data structure
create table student (
...
applied_date datetime,
transcript_received datetime,
recommendation_letter1_received datetime,
recommendation_letter2_received datetime,
rejected_date datetime,
accepted_date datetime,
started_classes_date datetime,
suspended_date datetime,
leave_of_absence_start_date datetime,
leave_of_absence_end_date datetime,
...
graduated_date datetime,
...
gave_money_date datetime,
died_date datetime
)
which is of course an academic example but that I've seen in many kind of e-business application. Nobody ever seems to think of it until later but two obvious requirements are: (1) query to see what state a user was in at a given time, (2) show the history of a given user. The code to do that in the above is highly complex and will change every time a new state gets added. The customer also has experiences like "we had a student who took two leaves of absence" or "some students apply, get rejected, apply again, then get accepted" When you find data designs like this you also tend to find some of the records are corrupted and when you are recovering the history of users there will be some you'll never get right.If you think before you code you might settle on this design
create table history (
student_id integer primary key,
status integer not null,
begin_date datetime not null,
end_date datetime
)
which solves the above problems and many others in most situations. (For one thing the obvious queries are trivial and event complex queries about times and events can be written with the better schema.) I can't decide if the thing I hate the most about being a programmer is having to clean up messes like the above or having to argue with other developers about why the first example is wrong.If "No code" is to really be revolutionary it's going to have to have built-in ontologies so that programmers get correct data structures for situations like the above that show up everyday in everyday bizaps where there is a clear right answer but it is usually ignored.
1. The first table structure is a flat non-normalized table structure that trades normalization for easy to query and select computed properties
2. Second structure is a normalized table structure that trades the normalization for joins.
It is easy to write a query for the first that gets a list of students names and the dates they applied. That query is harder for the second one. On the other hand figuring out what state a user was in at time t could be a very hard problem with the first table.
My experience with the first is that you find corrupted data records, one cause of that will be that people will cut and paste the SQL queries so maybe 10% of the time they wind up updating the wrong date. Systems like that also seem to have problems with data entry mistakes.
The biggest advantage of #2 is ontological and not operational, which is that in a business process an item is usually in exactly one state out of a certain set of possible states. Turns out that this invariant influences the set of reasonable requirements that people could write, the subconscious expectations of what users expect, needs to be implicitly followed by an application, etc.
Granted some of the dates I listed up there don't quite correspond to a state change, for instance the system needs to keep track of when a student started an application and when the last document (transcripts, letters, etc.) has been received. With 5 documents you would have 32 possible states of received or not and that's unreasonable, particularly considering that a student with just one letter and a very strong application in every other way might get accepted despite that. It's fair to say the student can have an "open application" and a "complete application". Similarly you could say the construction of an airplane or a nuclear power plant can be defined by several major phases but that these systems have many parts installed so if the left engine is installed but the right engine is not installed these are properties of the left and right engine as opposed to the plane.
which industry is that?
in general programming, absolute nope
not-DRY code can be weaseled out with a good ide
badly abstracted code, not so much
in fact in a way, DRY is the responsibility of the IDE not the programmer - an advanced IDE would be able to sync all the disparate code segments, and even DRY them if necessary
but when I read DRYed code, the abstraction better be a complete and meaningful summary, like 'make a sandwich', and without many parameters (and no special cases), or else I'd rather read the actual code
i understand the impulse to try to factorize everything but it just doesn't work beyond a certain point in the real world; it's too difficult to read, and there's always an 'oh, can you just' requirement that upends the entire abstract tower.
same same
i don't believe there is much evidence, certainly nothing conclusive, in this debate
but factorizing code concentrates the logic
that can be an advantage, to a certain degree, but it also reduces resilience, by specializing the code, and can reduce readability by forcing lookups of nested abstractions