DEV Community

Cover image for Stop trying to be so DRY, instead Write Everything Twice (WET)

Stop trying to be so DRY, instead Write Everything Twice (WET)

Conlin Durbin on December 18, 2018

As developers, we often hear cliched phrases tossed around like "Don't Repeat Yourself". We take ideas like this and run with them, sometimes a bit...
Collapse
 
nielsbom profile image
Niels Bom

Yes.
So summarizing: only make an abstraction for something if you can remove three repetitions.

Collapse
 
ssalka profile image
Steven Salka

I remember this as "Generalize at n = 3"

Collapse
 
n13 profile image
Nik

N = 2 leads to N >= 3 most of the time. I get the point and I also don't abstract out things immediately, but rather than follow hard rules, we can say that ... I don't know it seems like 99% of code is going to be used either once, or more than 2 times. Something to get used exactly 2 times would be very rare.

Also in general programming, abstraction isn't the only way to avoid repetition.

And the main reason for DRY is to not have to change the code in 7 places when something changes.

So you kinda have to know which things logically belong together. Very often that's things that share the exact same code, but sometimes it isn't.

Collapse
 
patryktech profile image
Patryk

A good reason to wait until you have three or more repetitions is that it helps you prevent writing abstractions that take ten arguments.

*You may think "oh, this code here repeated here, and here" so you write an abstraction. Then you add a third variation, and realize you need a height argument, so you redefine it with a default of None. Then repetition four requires a colour. And before you know it, your abstraction ends up with 10 different branches and becomes 🍝.

On the other hand, if you do write it in different places, you'll see what the differences are, and maybe abstract it into 3 didn't functions that only do one thing, so you don't end up with abstractions that you end up refactoring anyways.

(*Impersonal you - not saying you're guilty of that, as it gets easier to predict with experience, but good habit to develop early on).

Thread Thread
 
jondubois profile image
Jonathan Gros-Dubois

This is very well put. There are many ways to abstract something and you don't know what the best abstraction is going to be until you have a large enough sample of sub-problems to be able to make an informed decision.

Choosing the wrong abstraction is costly and leads to complexity bloat because developers have a natural tendency to keep adding new abstractions on top instead of refactoring or deleting existing ones.

Also, every time you invent a new abstraction to reduce repetition, you introduce one more concept that people have to learn in order to make sense of your code and it adds up; especially if the abstractions are contrived technical concepts and not strongly rooted in an underlying business domain.

Collapse
 
brain2000 profile image
Brain2000 • Edited

This only works if you have excellent communication between your devs. Otherwise if you have three devs, each recreating the wheel three times, that's nine repetitions.

It also depends on what the item is... if you are writing a way to find the next recurring date/time using an RRULE (RFC 5545), then you should definitely write that only once and have everyone use only that one. Otherwise one iteration will work if a daylight savings boundary is crossed, and the next two iterations will not, as an example...

Collapse
 
richistron profile image
Ricardo Rivas

I agree, once Sandy Metz said "it's easy to handle code duplication than deal with a wrong abstraction"

Collapse
 
n13 profile image
Nik

That's a good one! As much as I hate duplicate code - tearing apart an incorrect abstraction is a nightmare.

Collapse
 
nielsbom profile image
Niels Bom

Her talks and insights are great!

youtu.be/8bZh5LMaSmE

Collapse
 
darkain profile image
Vincent Milum Jr

I work on more than one web site at a time, all using the same base in-house library toolkit. For each of these sites, they all take a slightly different approach to solving various problems, sometimes related or sometimes not. Once two independent solutions emerge that look like they may be related, the two are pitted against one-another, and then merged together into a single solution, and then pushed into the core toolkit for the rest of the systems to have available as well. This method of parallel development has created significantly better software, because it forces multiple approaches to problems, rather than just a single approach.

The philosophy has been mostly the same as you suggest though, WET rather than DRY. If something is done once? Cool. If it is done twice? Odds are each one will have different advantages and disadvantages. When it comes to doing it a third time, the first two are merged into a single piece of code along with the new requirements, and pushed to the core library.

Doing this, I now have an extremely robust data processing system, user authentication system, path router, HTML templating system, and more all ready to go for future projects.

Collapse
 
rhymes profile image
rhymes

You're basically building your own framework out of your own business requirements, sweet! :)

Collapse
 
darkain profile image
Vincent Milum Jr

Yup, that's exactly it! And the entire framework is full open source BSD licenced on my GitHub account. The main issue is lack of documentation. This has been one of my main focuses this year and into next.

Thread Thread
 
rhymes profile image
rhymes

Good luck, after all that's how both Django and Rails started. They were frameworks used internally in companies

Thread Thread
 
ben profile image
Ben Halpern

In 2019 we plan on experimentally standing up more instances of the underlying platform that powers dev.to (Emphasis on experimentally)

I'm really excited to see what could come out of extracting this very high-level, highly opinionated framework for building online community spaces.

Collapse
 
andrewsw profile image
Andrew Sackville-West

Sometimes it is more difficult to modify an abstracted component to fit a specific use case.

This is a problem with your abstraction and not DRY. Another commenter mentions a similar problem with having lots of conditionals in their abstractions. That's another symptom of the sale problem, the wrong abstraction.

Also, I'd be concerned about your rubric, "haven't I written this before?" That really only applies when you are the only developer in the code. Hopefully, code review would catch this. I think you have to be a little more conscientious about this in shared code base since no one developer is likely to know all the versions of the same code there are floating around.

But, your point is still valid. Nice summary.

Collapse
 
armbraggins profile image
armbraggins

One of the arguments for doing it this way is that having three examples to abstract from is more likely to give a generally useful abstraction than the first two. It's not a hard and fast rule though - sometimes when you need the second instance, it's obvious there will be more later, sometimes you find several instances later that the abstraction could be improved and go back to the first few instances to refactor them.
And trying to force a specific use case that doesn't really fit to the wrong abstraction because otherwise you might repeat some bits of it is a symptom of the same problem. Sometimes it means your abstraction would benefit from having parts refactored out separately, sometimes it's better just to have a comment saying "this looks like , but that's not a good fit because ". But if you find the same comment being written three times, rethink the refactor....

Collapse
 
wuz profile image
Conlin Durbin

Also, I'd be concerned about your rubric, "haven't I written this before?" That really only applies when you are the only developer in the code. Hopefully, code review would catch this. I think you have to be a little more conscientious about this in shared code base since no one developer is likely to know all the versions of the same code there are floating around.

This is a super valid point! It's a concern with DRY as well, since if multiple developers don't know the full codebase, they are likely to abstract something multiple times. To me this all comes down to making sure you have well commented code and making team communication easy. Code Reviews also help you catch these problems before they hit the main branch.

Collapse
 
mahlongumbs profile image
Mahlon Gumbs

Articles like this really bother me. Anything that can be done, can be done poorly. That, by itself, is not a reason to avoid doing said thing.

Understanding how and when to create abstractions is not trivial. The end result may seem trivial at times but the skill is something learned and harnessed over time (sometimes over a career).

DRY, like most concepts in software, is more of a guideline rather than an unbreakable rule. Like all good guidelines, you violate them if you have good reason to do so and with a solid understanding (and acceptance) of the consequences.

An article suggesting replacing concept A with concept B because sometimes concept A is done incorrectly adds to the noise developers must learn to filter out as we hone our craft.

For example, it makes no sense to write something again if it is identical to the first. So telling someone to write everything twice is just as bad as misrepresenting what DRY is.

It is better to spend the necessary time it takes to understand your situation and apply the concepts that makes sense there than it is blindly start going against sound guidelines just because it may seem easier.

As an example, consider our old friend the RDBMS. A WET DB can lead to some pretty severe data issues. One would not break normalization on a whim. However, because of the needs of a data wharehouse, a fully DRY DB would be catastrophic; so denormalizing for this very specific use case makes sense. The consequences here a fully know and accepted.

^ RDBMS? Dude! Just use Mongo! :-)
I know. I know. But the point remains the same.

Understand your situation and apply the tools and concepts (and possible modifications to said tools and concepts where reason demands it) that best fits.

Collapse
 
crius profile image
Claudio • Edited

I can only agree with you.

Also, this WET concept is basically taking a guideline so simple "Do not write things twice, if you can" and suggest a more complex guideline "Write things twice, but not thrice!"

Really? how's that supposed to be easier to remember and follow?

As the old philosophers suggest, we should aspire to the best possible, not the average. Because we're flawed, we will never achieve the best possible but certainly we will achieve better result that whom who tries to achieve the average.

Oh, and the DIV example is horribly misleading. That is not an exception to DRY.

Collapse
 
xtrasmal profile image
Xander • Edited

Just wanted to leave this here for people who are interested.

"DRY is about Knowledge, Code duplication is not the issue."
verraes.net/2014/08/dry-is-about-k...

Collapse
 
emilper profile image
Emil Perhinschi

Finally some sense in an article and comment thread filled with anti-sense.

You don't "abstract" to avoid duplicating code, you abstract to give names to bits of code so you can think about them easier; for example you don't make functions to reuse them, you make functions to give a name to that operations so you'll think about "create_basket" instead of whatever the basket code does, and work with that abstraction. You don't even need to have that function called twice, once is enough.

Reusing inapropriately code previously abstracted in an object or function might be bad, but "don't abstract unless you write it twice or three times" is worse.

Collapse
 
jondubois profile image
Jonathan Gros-Dubois • Edited

True, but many behaviours cannot be boiled down to a three word function name without causing even more confusion and indirection. Sometimes the raw code itself tells the story best. You don't want to force the reader to jump around between different files or parts of the code to figure out how the code accomplishes a certain simple task.

Maybe if the author of the function knew how to find the perfect words to express the exact behavior of the function in a way that everyone could intuitively understand, that would be great, but this is not reality. Human psychology is not so simple - There will always be people who will intuitively misunderstand no matter how careful and precise you are in your choice of terminology; and in these cases, your abstraction will cause indirection and confusion.

Very often, the author uses the wrong sequence of words which have multiple interpretations and this only serves to confuse and misdirect the reader. Writing the correct abstraction requires familiarity with the sub-problem and this familiarity can only be achieved through being exposed to the same sub-problem multiple times, more than 2 times, sometimes even more times.

Also, with a sample size of 2, you cannot always know what kind of abstraction you'll end up needing... Maybe as your system evolves, these 2 currently similar behaviors will diverge rather than converge and sharing an abstraction between them won't make sense; it will mislead the next developer down the wrong path.

Collapse
 
katylava profile image
katy lavallee

This is true, but you have to be willing to spend some time making sure you came up with the right name. Which is of course one of the hardest things in programming ;).

Collapse
 
wuz profile image
Conlin Durbin

This is a great link! Thanks so much for sharing. Gonna update the post to add it!

Collapse
 
melezhik profile image
Alexey Melezhik • Edited

On code duplication. I am "lazy" programmer. Every time I start writing a new code I don't care about code duplication. Human's brain tends to duplicate things, it's just tedious to think about good abstractions from the very beginning. However one day, as proper time comes or when I start getting tired of copy/paste (: I look at my code and start refactoring it making good abstractions and reducing code duplication.

Collapse
 
n13 profile image
Nik

Same here - I think you're a good programmer :)

Once some of the code has already been written, the problem space and the solution become clearer, and then it's also easier - and makes more sense - to create abstractions.

It's best to realise that when you start programming, you only know about 40%, if that, of the problem space. As we create a fitting solution for the actual problem we learn the other 60% that we were ignorant about when we started. The more we know about the problem space, the better, or more precise, the abstractions are going to be.

Collapse
 
nicoteiz profile image
Nico Oteiza

I love the concept in this post thanks Conlin :)

I also am very in line with the "lazy" programming style. I think it's more intuitive, and helps to not fall on the trap of wrong abstractions, as Ricardo Rivas also commented

Collapse
 
bloodgain profile image
Cliff

I get what you're saying, and sort of agree, but I think your examples show a misunderstanding of DRY.

The point is not how long it takes a dev to create a stylized button. The point is that if the button is to be used in several places, there's one official representation of it. That way, it's consistent everywhere, and if it ever needs to be changed, it can be done in one place instead of all over your application so you can miss 3 instances of it and end up with an inconsistent interface. That's a UX example, of course, but the principle is the same for non-visual applications and their underlying APIs.

Collapse
 
wuz profile image
Conlin Durbin

I agree with you! I may not have made it super clear, but I think DRY when applied correctly isn't bad. The examples are definitely a exaggeration, but I think the exemplify the style of DRY programming that can happen when you don't agree on how to abstract or define when exactly you shouldn't repeat yourself.

I think having a rule like "Write Everything Twice" is actually the right way of achieving the same end as DRY. You get good abstractions and you don't spend a ton of time trying to generalize the components you don't need to generalize.

I would argue that it is better to have to miss a single instance and have to update it later than to have too many abstractions that have to be really general or abstract to function.

To me, the best abstractions are ones where you don't feel like you are working with an abstraction. Instead it just feels like the "right" way of accomplishing what you are trying to accomplish.

Collapse
 
mrbenj profile image
Ben Junya

So spot on. Thank you for this.

Some devs go to crazy lengths to not write something twice. It's honestly a little silly :).

Collapse
 
nunez_giovanni profile image
Giovanni Nunez

This! And then you have to deal with their crazy abstraction, because they think method names should be the only real “comment”
Sorry, anecdotal example / rant 😛

Collapse
 
squidbe profile image
squidbe

No need to apologize. You are not alone. :-)

Collapse
 
juliuskoronci profile image
Igsem

I am more of an advocate of common sense. Experienced devs will write the abstraction straight away but it is never as simple as DRY or WET. If you have 2 use cases you should always think about DRY but when considering this, I try to look at a few things:

  1. How much time it takes to create the abstraction - if I need to spend a week to abstract something I can write in 10 minutes there is no point in abstracting
  2. How likely it is that I will need to reuse the logic - by likely I don't mean in my head but in actual requirements, designs etc.
  3. What are the differences between the 2 use cases and the possible 3rd one - often you find yourself stretching the Abstraction just to fit in a similar use case and that is very bad. A better approach would be then partial abstraction and abstraction composition

So don't be DRY or WET but be smart and use your common sense and experience :)

Collapse
 
n13 profile image
Nik

It's not about whether it's the same logic, it's about whether it's the same thing.

If your app features a yellow submarine and a banana, you wouldn't want to abstract out the colour yellow as they're two totally different things.

Collapse
 
juliuskoronci profile image
Igsem

Well you would abstract out the color into a colors constants TBH :D

Collapse
 
paddy3118 profile image
Paddy3118

Be moist. Exactly.

Collapse
 
denishowe profile image
Denis Howe

I once had to update some SQL in a program and found it had been duplicated four times. No, not four copies, 16 copies! Anyone who thinks that maintaining even two copies of a piece of code is easier than one subroutine or function should not be allowed in a team.

Collapse
 
wuz profile image
Conlin Durbin

Anyone who thinks that maintaining even two copies of a piece of code is easier than one subroutine or function should not be allowed in a team.

Sorry you feel that way Denis! In my experience, the best teams are those that have constructive conversations around these kinds of concerns! I definitely wouldn't want to have to work on that much duplicated SQL, but I still believe there is only so much usefulness to be gained from abstraction. This sounds like a situation where the code would have benefited from abstraction after the 3 time it was duplicated.

When would you draw the line around abstraction? Would it be after just the second time you had to write it?

Collapse
 
denishowe profile image
Denis Howe

Sorry, I explained badly. The original code was duplicated so there were two copies of it, then those two were duplicated so there were four copies, and so on, doubling each time. (There were some minor differences between copies that were easy to parametrise).

The point is that each copy and paste was a crime in itself, even the first one that only created two copies of a fairly short bit of code.

Saying to yourself, "I'll abstract that later if I need to" is just not good enough as it risks the copies diverging in ways they shouldn't and leaves it up to the next guy to find, understand and fix your mistake or to perpetuate it by applying the same fix in multiple places. Experienced programmers avoid duplication because it makes maintenance harder.

Collapse
 
stealthmusic profile image
Jan Wedel • Edited

I think you forgot to mention important point when it comes to DRY: Dependency. The more code is reused at different places the more dependency you create. This is especially an issue when it comes to refactoring pieces of code into a library. Then you need to maintain your lib, update other services that use that lib, even maintaining multiple versions. So DRY across service boundaries is mostly not a good thing.

A second aspect is

You must comment your abstractions

I am personally a big fan of clean code and this would be a violation to the principles. If you want to add comment, you should refactor or rename the code in a proper manner that reflects what you wanted to comment. If it’s hard find a proper name, then the code probably does too much and violates other principles. If the abstraction is too complex too understand, it probably need to be removed.

Collapse
 
squidbe profile image
squidbe • Edited

"I am personally a big fan of clean code and this would be a violation to the principles."

Be wary of dogma. Principles are important in that they help provide a common understanding across a team, but principles are, by definition, high-level --
the reality is that not everyone will agree on what constitutes proper naming or when code does too much. The occasional code comment can go a long way to provide clarity, particularly when there's some unobvious business reason for what might look like strange logic or naming.

Collapse
 
mrchedda profile image
mr chedda

If naming a method is too difficult, it is likely doing too much. IsObjectEmpty() is pretty clear. GetProducts() is pretty clear. GetReviews() and they should be doing exactly what its name states. Reading these comments makes me wonder if any of these people have a CS degree.

Collapse
 
stealthmusic profile image
Jan Wedel

I agree, naming is hard. But it’s usually worth it. In my team, we only do comments for ugly workarounds with a link to an issue tracker to check if the causing problem has been resolved. Everything else has proper method names. We do that for years now and it’s working great for us. Once in a while, I have to look at other team’s code and find a lot of abandondoned comments that stating misleading or even wrong things in contrast to what the code actually does.
And yes, principles can be ignored in certain situations, but I found almost all clean code principles very helpful worth following.
I thinks the saying is „you have know the rules to break them“...

Collapse
 
lgraziani2712 profile image
Luciano Graziani

Thank you for putting in words something I try to preach within my team!

Collapse
 
thomasjunkos profile image
Thomas Junkツ

My personal take: abstract, when it sucks.

I don't care if code is written once, twice, thrice etc. Sometimes things are a bit boilerplatey / repetitious, which are a good candidate for refactoring. But unless it doesn't suck to write things over and over there is no need to abstract.

A good indicator for suckiness is how error prone things are: if there is potential error prone code in more than one place, debugging really sucks and then you should do yourself and others a favour in centralizing this part.

OTOH to get this right, you need discipline and experience to not confuse it with sloppiness.

Collapse
 
owenfar profile image
Owen Far

I don't know how this article made the subject of the weekly newsletter: "DRY is out, WET is in". This is really not a good practice to follow. I think we should simply understand the basic term of DRY and that's it. When you say duplication is not a problem, things might get out of hand. I agree with some of your discussions, but I still feel that the DRY term has its own importance in both the programming and development world. If you code something twice, it also means you have to maintain and update something twice. This argument can be indefinite.

Collapse
 
maciek134 profile image
Maciej Sopyło

This article is really dangerous to more junior developers. Copy, paste and change some stuff invites problems, because there may be 5 developers working on one project and if all of them do it just once you have 5 repetitions of something that should be abstracted away. Developers are not monkeys, you should think if something needs abstraction before doing anything, not after you repeat it three times - otherwise you end up with maintainable piece of crap that will need to be rewritten at some point (I had to rescue such projects, it's not fun). If you spend some time actually designing the system you won't have to spew code left and right as you go.

Collapse
 
nielsbom profile image
Niels Bom

“Premature optimization is the root of all evil” - Donald Knuth.

wiki.c2.com/?PrematureOptimization

Collapse
 
angsuman profile image
Angsuman Chakraborty

Yes. I take it an opportunity to redo a second time something which I found an way to improve and then refactor the first instance with the new version.

Also to develop a complex piece of code I do it iteratively. First get it working and then iteratively improve it to my satisfaction.

Collapse
 
mindlace profile image
Ethan Fremen

A slight correlary, in my mind, is “you’re going to throw away the first one”. Now, what constitutes that one differs like you outlined, but I think this principle serves to reinforce the “write everything twice” - if you’re working on the MVP, no matter how much duplication you have in it, you’re going to be rewriting it later, so save it for later. (But still apply WET in the second round! The Second Version Will Be Perfect is a notorious tar pit.)

Collapse
 
haikieu profile image
Hai Kieu

When making your code too DRY, it gets burned. :)

You should balance between DRY and WET. I have seen some developers trying to make their code extremely DRY by compressing all the logic, after the process, no anyone can understand the logic. It usually makes the code unreadable. It also usually ends up adding more parameters to the method and make the logic unpredictable and untestable with vary combination of inputs.

Collapse
 
beesnotincluded profile image
beesnotincluded

Excellent backronym!

Collapse
 
swfisher profile image
Sam Fisher • Edited

I neither agree nor disagree but I love a good challenge to dogma! Principles by themselves will not create good software. They only point to helpful ways of thinking.

We can end up with too many small abstractions when we abstract too readily and too much spaghetti code when we abstract too slowly. The only thing that will save us is thoughtful design! Oh no! Haha.

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

As long as you can extend or refactor any piece of code -if needed- there's no point on typing things down twice. By the way it's not as easy as writing things one or twice, let me explain:

To start with, i'll address your ambiguous concern. It's about functionalities, not logical entities. On a sum, let's say 3 + 4 = 7, both 3 and 4 are entities so it's the result and the logical evaluation of that mathematical statement is true.

Having a function/method to sum two numbers is a functionality, let's say it's implemented like this:

function sum(num1, num2) {
  return (num1 + num2);
}

If you want to extend this functionality because the requirements changed you can simply refactor it and ensure through tests that the original behaviour is still working properly while adding your new stuff like this example:

function sum(...numbers) {
    return numbers.reduce((accumulator, current) => {
        return accumulator += current;
    });
};

This will lead to a single function that covers the possible use-cases without having the need of breaking apart into more than one function, because it only does 1 thing (you can learn about that searching for Clean Code) it was summing 2 numbers and now it sums N numbers, as N can also be 2, it does the same that it did before plus the ability to sum a number of items greater than 2.


Sometimes it is more difficult to modify an abstracted component to fit a specific use case. You add a lot of complexity or you break that component out into something new - which isn't super DRY. You can't know every use case for your component on day one.

Harsh statements here xDD
If you find a function/method, being abstract or not, that does many things and you only need one or some of them, you should refactor it into small functions with single responsibility, then adapt the tests for it and it will do exactly the same it was doing when coded first. No DRY issues here (the outgoing is a refactored function/method, probably break down into smaller functions or methods, not duplicity). Then you'll simply use those little pieces that you need for your feature/refactor/task...

By the way the use cases should be documented, otherwise there are issues with consultants, clients and so on because they forget about functionalities and request them again, then if the dev who take care of this request is not aware of that feature he will repeat this feature again (and this can happen N times). So you are spotting lack of documentation issue here, nothing to do with code or dev's fault, devs are not who should document features, this is a task for consultants.
Devs should of course, create technical documentation related to this functionalities, specially when complex processes are involved. And no, a code so clean that is being self-documented is not enough when the project grows.

By the other way, for a given feature, fix, task, refactor and so on, someone had to take time to analyse -hopefully-the current situation, find out the best -or at least a good- solution, implement it, integrate it, this code would probably be reviewed, tested from different points of view (unit and end-to end tests, user interaction and so on) and then issues, concerns and errors will be pointed out so this individual or group of devs can secure/fix/add the needed use-cases and finally it will be merged into the release branch of this product line.

Imagine doing this twice or thrice, and get as a result multiple implementations of the same functionality in which some of them may not cover all the needs, then other devs (and yourself in some weeks) will be struggling to know which method/function use to reach a given input/output, loosing time reading all those repeated code implementations guessing yourself things like:

"Should I use numberAdder method or maybe sumNumbers... oh there's another one called sumNums!"
"is it ok to use this one that receives an array or this other one that works with objects?"
"Maybe this one that returns an array of lists would be ok in comparison with this other that returns a matrix..."
"what they do different from one to another apart of having different data structures for inputs and outputs?"

The WET acronym as opposed to DRY is funny but that's all, nothing to use IRL.

Even trying your best for DRY it will be repeated things on a big project (never saw a big project without repetition in my entire career) imagine the mess of ignoring this principles...

And to end this little bible (sorry for that) you need to take in mind that when coding "hello world"s and little apps for fun at home anything will work well, specially because probably you'll code it and forget about it in 2 weeks to never coding over this side project anymore, but when dealing with big projects, real projects that move millions of U$D a year with tones of users, customers, devs, consultants, sysadmins, UX engineers, designers and so on involved any little change makes the entire project development process more or less efficient and can lead to multiple disasters or good endings.

I also like to accompany DRY with KISS -which if you didn't know about it stands for Keep It Simple, Stupid!- principle, it leads to a better dev experience :D

Collapse
 
wuz profile image
Conlin Durbin

Nothing you've written here disregards the idea of writing everything twice. With your sum example, you've taken an exceptionally trite example - adding numbers - to support the idea. What I was talking about with this article was writing real code that handles real business concerns. Often times those functions and components involve complex logic that often time seems duplicated on a surface level, but contains a number of nuances.

As far as refactoring complex functions into smaller functions, once again this only really works if you can adequately determine that the functionality of that complex function - otherwise you might break those out into small functions that don't work together the same way as the original complex function. Side effects, database calls, etc exist and breaking down functions doesn't always just mean breaking them into component parts.

As far as documentation and testing, its a rather bold assumption that most companies have the capacity/foresight to do those things. The idea that consultants should document the developers work is just wrong by the way. Introducing another layer of complexity, this time on an organizational level is a horrible idea.

Funnily enough, you've highlighted the exact problem with trying to DRY out your code too early:

"Should I use numberAdder method or maybe sumNumbers... oh there's another one called sumNums!"
"is it ok to use this one that receives an array or this other one that works with objects?"
"Maybe this one that returns an array of lists would be ok in comparison with this other that returns a matrix..."
"what they do different from one to another apart of having different data structures for inputs and outputs?"

These are all examples of implementation and logic changes that were probably made for a specific reason at a specific time. Assuming you know best how you write an abstraction that handles all of these things, before seeing 2 or 3 use cases of all of these things is a recipe for disaster and a perfect example of premature abstraction.

Finally, telling me I "need to take in mind" that hello worlds and side projects aren't good representations of enterprise software is, at best, rude and, at worst, exceptionally arrogant and assumptive. I've been writing enterprise software for companies with large eng teams for years. I've seen the dangers of premature abstraction far more than I've seen code with too much repetition.

The moral of this whole thing is that following anything to the point of dogma is dangerous and leads to negative impact on your team. WET tries to give you a good level of ambiguity to decide a point at which code can be abstracted without being to dogmatic. WET doesn't really care if you write it twice, thrice, or four times. Just know that you need a good sample size before abstracting out a complex bit of business logic.

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

How it works

First of all, the sum example works well because it does a single thing, that's what your functions/methods are mant to do, a single thing, so it can be applied equally to any code because a complex process is just a group of many little things put one after another.

A scientific method works the same when adding 1 test case than when adding 93247829.

Is this the issue?

What I was talking about with this article was writing real code that handles real business concerns. Often times those functions and components involve complex logic that often time seems duplicated on a surface level, but contains a number of nuances. As far as refactoring complex functions into smaller functions, once again this only really works if you can adequately determine that the functionality of that complex function - otherwise you might break those out into small functions that don't work together the same way as the original complex function. Side effects, database calls, etc exist and breaking down functions doesn't always just mean breaking them into component parts.

This is the most stupid thing I've heard in a long time. Try to remember some subjects from the college such logic and then let's apply it to an agnostic example:

// "complex function to deal with business needs"
function featureX(arg) {
  const data = getThings(arg);
  let cookedData;
  let useCase;

  if (BusinessLogicCondition) {
    cookedData = data.split(";");
    useCase = 1;
  } else {
    cookedData = data.split("#");
    useCase = 2;
  }

  let relatedData = [];

  if (useCase == 1) {
    this.Obj.IdProp = cookedData[cookedData.findIndex("userId") + 1];
    let extraData = DBQuery(`select * from table1 t1 where t1.userId like ${this.Obj.IdProp}`);
  } else if (useCase == 2) {
    this.Obj.IdProp = cookedData[cookedData.findIndex("companyId") + 1];
    let extraData = DBQuery(`select * from table2 t2 where t2.companyId like ${this.Obj.IdProp}`);
  }

  relatedData.push(extraData);

  cookedData.forEach((el) => {
    doSomethingWith(el);
  });

  render(relatedData);
}

let's break things down:

const { render } = require("node-sass");

BusinessLogicCondition = true;

function featureX(arg) {
  data = getArrayFromString(args);
  setCurrentIdProps(data);
  relatedData = getRelatedData();
  doSomethingWithData(data);

  render(relatedData);
}

function getArrayFromString(str) {
  return checkBusinessLogic() ? data.split(";") : data.split("#");
}

function checkBusinessLogic() {
  return bussinessLogicCondition ? true : false;
}

setCurrentIdProps(data) {
    checkBusinessLogic() 
    ? this.Obj.IdProp = data[data.findIndex("userId") + 1] 
    : this.Obj.IdProp = data[data.findIndex("companyId") + 1];
}

function getRelatedData() {
  return checkBusinessLogic() ? getRelatedUserData(this.Obj.IdProp) : getRelatedCompanyData(this.Obj.IdProp);
}

function doSomethingWithData(data) {
    data.forEach((el) => {
        doThing(el);
    });
}

I've had a hard time coding the first example tbh and I don't want to loose much time with that, i could show you real complex (really complex actually) business code from a software that more than 100 multi-billionaire companies are using, each with it's own use-cases and config, but a confidentiality contract forbids me so we will have to settle for this example.

Now imagine you need another feature or use case, in the first dirty/shitty example you'll need to put some conditions and dirty behaviour in between the current feature code or repeat the entire feature just for this.
On the other hand, in the second case you can add whatever you need as single responsibility function and then call it in the middle of your stuff, whenever it's needed.
If this new function edit things that other following function needs you can see it easily by checking the current code as it can be read like prose, you read this and you think:

"ok, this is the feature I need to edit, let's see what's up in there, oh ok, it gets the string, convert it into an array splitting by one char or another, ok it sets the props using this received data ID depending on it's company or user, then it gets related data from DB using the same condition, then it does that with this received data (such filters or whatever) and finally it renders the data".

function featureX(arg) {
  data = getArrayFromString(args);
  setCurrentIdProps(data);
  relatedData = getRelatedData();
  doSomethingWithData(data);

  render(relatedData);
}

It will be same readable even when growing because your main function/method has well semantically defined function/method calls that you can read and understand easily one following another. When you read something like that you don't want or need further knowledge that what the main function is telling you.

If you have a function called getArrayFromString but you need to do another function to get an array from a string with different conditions, you can either extend this current one (never exceeding 3/4 args, because if you are exceeding 3/4 args, much probably you can separate this into two different functions/methods) or refactor it like getArrayFromString / getArrayFromQueryString or maybe getArrayFromStringFeature or even getArrayFromCSVString / getArrayFromTaggedString.


The real issue: Our mind in development process

Out of examples, let's go deep in this to address the real issue. We -in our head- code features, most of them conditioned to a state.

Our mind works like this when trying to express ourselves on a way or another, getting considerable chunks of information and understanding a surface overall to find a quick solution to a given problem, while we have a hard time going deep in each information chunk, where we need to strive at the time of talking/writing.

But when reading information it's just the opposite, when we read surface information our brain begin to ask questions and beg for details because we feel the information incomplete.

This is why it's easier for us to code like the first example but refactor it to look more like the second one needs some effort.

It's not bad to code like the first example, it's easier to us, the half of our job is to get things done, the other half is refactor the things that we get working to be more readable and maintainable, thus if you "code, struggle, code, get things done and jump into another thing", you can seem efficient -today- but the entire team will struggle to get things done, release new features and fix bugs in a not-so-far future.

Once you talked about Try to DRY your code too early and premature abstraction I assumed you either have few or little experience with complex projects or get stuck in a bad-driven one for years... If you abstract something taking in mind the current situation and something new needs to be done that interacts with this abstraction... REFACTOR THE ABSTRACTION! and if you refactor your code once you have the things done to "DRY it" you'll end up with a good yet extensive API that, if well documented, the entire team can consume to keep the things ordered and in peace.

Of course the most important thing in this is to keep your tests at the top of the entire process, because if you don't test your software or you have some tests but are outdated, you can't trust your tests, and if you can't trust your tests, you will apply the "if it works, don't touch it" rule, which means that you are afraid of the code, you are afraid of breaking some business process, cause bugs and so on. If you can trust the tests, you'll be able to refactor your code and if the test says it's ok, it's ok.

Our MAIN job as developers is to automate things, not just for clients but also for ourselves thus if you need to code things twice or thrice or... because you want to avoid issues, your project had very bad decisions regarding the underlying architecture, design patterns, methodology and so on.


How to work

There are few ways to achieve a good software product, I'll set one here, you can search for alternatives online:

There are basic rules that can help you with that:

  1. A function/method name should contain a verb
  2. A funciton/method should stick it's concerns to this verb and scope
  3. A function/method must do a single thing

then just follow a workaround that lets you work well:

  1. Code tests first (preferably)
  2. Code your features the way you want till they are working and the tests are passing
  3. Refactor your code according to the method/function rules before
  4. Ensure your tests are good with the refactored code or code the tests now if you didn't on step 1

If you avoid tests or refactor, consultants avoid doing feature documentation and all this avoidance is to get things done quickly, you'll be struggling in a future to get things done, team members will burn out and eventually leave the project. As it's not documented not even well coded, each dev leaving means that you lose valuable knowledge, you need to pay more to the current devs to make them stay here, then they keep here for the salary but are burned anyway so they don't want to struggle refactoring anymore and simply add more and more code to get the things done. The end result? well... at this point you probably already figure it out

Thread Thread
 
wuz profile image
Conlin Durbin

lol ok

Collapse
 
aghost7 profile image
Jonathan Boudreau

I believe this is referred to as the rule of three.

Collapse
 
carlosmgspires profile image
Carlos Pires

You are confusing two very different concepts.
DRY is not about code: it's about the model (representation).
It's about having a "single source of truth". There is no "premature optimisation" whatsoever in that. It's just good programming practice.

What you are talking about is implementation and levels of abstraction. There you can have premature optimisation and over-engineering, and you sure need to be careful with that. But it has nothing to do with DRY.

Collapse
 
gtanyware profile image
Graham Trott

Rather than be prescriptive about the number of repetitions I would go for the gut reaction. If it tells me that repeating one or more further times will eventually cause me more effort than to stop now, wind back and code a single replacement, then it's time to do the latter. There's no rule that can (or needs to) be slavishly followed; we're supposed to be experts and able to make optimal decisions of this nature.

Collapse
 
mrchedda profile image
mr chedda

Sorry but the fact that you allow something to be written twice is terrible. I see NO valid and good reason to have duplicate code anywhere. All it does is make it difficult for others to contribute should requirements change in the future. If abstracting something out is difficult for anyone they should find another profession.

Collapse
 
ben profile image
Ben Halpern

Fabulously articulated Conlin.

Collapse
 
wuz profile image
Conlin Durbin

Thanks Ben!

Collapse
 
bglick profile image
Brian Glick

OMG, thank you. I've been trying to get this out of my brain for years.

Collapse
 
lorenzoattrexin profile image
Lorenzo De Leon

I once attended a seminar led by Dr. Ralph Johnson (of GoF fame) and he said that we shouldn't consider writing a library or framework until we've repeated using it at least 3 times. So perhaps we should consider Write Everything Three Times?

Collapse
 
ddiukariev profile image
Dmitry Diukariev

Now if you have X devs in a team you can end up with up to X*2 copies of same knowledge/assumptions. When any such assumption turns out to be wrong you have to change X*2 places, but that's not a problem. Problem is finding all those copies. When system gets large this task goes from easy to impossible.

But I guess it is an OK rule for tiny teams and tiny software systems.

Collapse
 
satrun77 profile image
Mohamed Alsharaf

Good post! I personally prefer to repeat as needed better than one complex code with several conditions just to accommodate all known use cases. Repeat makes code easier to maintain and understand.

Collapse
 
bloodgain profile image
Cliff

This is usually a sign that your abstractions are weak and you should consider refactoring the code around what's repeated, too. Often it means you have too little abstraction and too much specialization in your functions/classes/etc.

Though as Niels points out, you might be trying to de-duplicated code that's not truly duplicated. Always question the code first, but if you can't come up with a better abstraction, it may be best to leave it for another time. I do agree that a little duplication is better than an over-complicated abstraction. The goal is to consolidate the knowledge and authority to one implementation as much as is reasonable.

Collapse
 
satrun77 profile image
Mohamed Alsharaf

I agree! question the code first before abstracting code :)

Collapse
 
nielsbom profile image
Niels Bom

If I understand you correctly you’re saying: if I abstract away code, but in that code I have to cater for a couple of different use cases the code inside the abstraction becomes too complex, right?

There’s two answers:

1: it could be that the code you’re trying to deduplicate is not similar enough, in that case don’t deduplicate.
2: there are ways (patterns) to make the code inside your abstractions less complex (basically: less conditionals). For a nice illustrated example of this: youtu.be/8bZh5LMaSmE

Collapse
 
okdewit profile image
Orian de Wit

For me, personally, it depends on whether the non-dry stuff is a true repetition of business logic, of the domain specific stuff, of the requirements set forth by end users.

If people in the business give it a name ("the user registration process", "subscribing to a tag") it must be dry, even if only used twice, to avoid bugs.

If the repetition however is due to composition, further abstraction often hurts the code. You might often remove the first item of array, then filter invalid stuff, then reduce it to an aggregate — doesn't mean it must hide inside a tailFilterInvaludReduce() method. Leaving the composable bits composable is often a good thing, because method names can lie about their contents.

Collapse
 
shiraazm profile image
Shiraaz Moollatjie

Interesting article Conlin!

I think that team sizes also affects WET and DRY. WET might not be preferable for bigger teams because you need to acknowledge that you wrote or at least saw something similar before.

DRY on the other hand, is arguably currently dealt with using code analysis tools (and probably why 'knowledge' takes a back seat). So the use of code analysis tools here makes it easier to implement in bigger teams. You can argue that you can do the same in WET though.

What's your thoughts on this?

Collapse
 
olddutchcap profile image
Onorio Catenacci

Honestly we'd be far better off if too many people tried to DRY out their code. I don't know who it is that you work with but I see copy/paste garbage all over most codebases I look at. The people I'm working with are in no danger of making their code too DRY. And I don't believe I'm alone in that.

Collapse
 
jasondown profile image
Jason Down

I've also heard this referred to as the rule of three.

Collapse
 
merklegroot profile image
Merkle Groot

The DRY principle isn't about saving time; it's about preventing your logic from diverging.
Of course it's faster to copy paste than it is to create an abstraction.

If you suddenly change your logic to prevent non-admin users from deleting items on the list page, but that logic is implemented a second time on the details page, what do you think will happen?

DRY is meant to save you from those kinds of problems.

Collapse
 
soundarmoorthy profile image
Soundararajan Dhakshinamoorthy

I call this DRY (Do, rather yanking).

A lot of attempted DRY code i saw, have enough parameter additions, and conditional statements.

Collapse
 
paddy3118 profile image
Paddy3118

I'm for M.O.I.S.T myself.

Collapse
 
gmartigny profile image
Guillaume Martigny

Make Organization Inconsistent and Stiff Teamwork ?

Collapse
 
paddy3118 profile image
Paddy3118

Not even close.

Collapse
 
junaidameen profile image
Junaid Ameen

I think WET applies more from a UX perspective, but if you are doing programming in c# or java or any object oriented language, DRY principle applies.

Collapse
 
rhymes profile image
rhymes

Thanks for giving it a name and for the fantastic explanation!

Collapse
 
userguest08 profile image
userguest08

DRY it's from OOD principle and those principles they are the most powerful design we use to enhance back-end, your suggestion is not valid for this side of programming, it may apply on UI/UX.

Collapse
 
wuz profile image
Conlin Durbin

While I do think there is more need for DRY principals on the backend, there are many functional languages that do not use OOP at all. Even within OOP languages like Java, premature abstraction can hurt you. Abstracting two classes into a higher order class could cost you if the abstraction is too generalized.

Collapse
 
userguest08 profile image
userguest08

OOD is not OOP first, OOD is general design patterns for software development, you can use OOD in many language that they are not oriented object i.e php...
For abstraction you may right, but also OOD handle that in general matter.
The rule of three that it's similar to your suggestion can apply also with DRY, it seems opposition but DRY doesn't really means DON'T REPEAT THE CODE Like many understand, DRY is DON'T REPEAT YOUR SELF in intelligent manner so when low level piece of code have same sense in one module create abstraction for it.

Collapse
 
tyagow profile image
Tiago Almeida

I would love if my team start doing this, right now it's DRY for every single piece of line

Collapse
 
cirotix profile image
Damien Cirotteau

Probably the best advices I have ever read about abstraction of code
programmingisterrible.com/post/139...

Collapse
 
lazarljubenovic profile image
Lazar Ljubenović • Edited

So we can repeat

's without much issue and I don't think anyone will take offense at it.

...

Divits spotted.

Aaaand markdown is broken. Way to go!

Collapse
 
diek profile image
diek • Edited

I always do that, i allow me to repeat code two times, if i need it by third time, i refactor it.

Collapse
 
6temes profile image
Daniel

There should be a tag called #common_sense and this article should be assigned to it.

Collapse
 
incrementis profile image
Akin C.

Hello Conlin Durbin,

thanks for your article.
It helps me understand the WET principle better.
My professor recommends your article and it helps me to prepare for my software architecture exam :D.

Collapse
 
biros profile image
Boris Jamot ✊ / • Edited

I call this "Pragmatic Programming", and it's the good way to go with the design principles.

Collapse
 
tgs profile image
Thomas Smith

Ned Batchelder has a saying that "the right time to automate something is when you're tired of doing it by hand." Similar gut-feeling basis.

Collapse
 
cmilkau profile image
Carsten Milkau

"you could just copy/paste and change the code you need"
The source of at least 80% of the bugs I see every day. And yes, most of the time it is indeed just one copy.