DEV Community

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

Posted on • Updated on

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

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 too far.

Stepping back and evaluating why we do these things is helpful. So today, let's look at an alternative ideology to DRY programming.

Don't Repeat Yourself (DRY) programming, defined

DRY is defined (according to Wikipedia) as:

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

Some of this might get a bit pedantic, but that can be helpful when considering something like this. Let's break down the parts of the phrasing there.

Every piece

What is "every piece"? Can we never repeat a variable name? An HTML entity?

Ok, ok. So we can repeat <div>'s without much issue and I don't think anyone will take offense at it. But this does bring up the question - when do we decide something has become a "piece of knowledge"? In React, a good example might be a component - but is that PrimaryButton and SecondaryButton or does it mean a generalized Button class? The answer is generally considered to be "Whatever your organization chooses", but this can still leave a good bit of ambiguity around what we choose to abstract.

knowledge

This is another ambiguous point - what do we define as knowledge? Consider a styled button element using some atomic classes and React. If it takes a senior dev 10 seconds to create , they may not consider that knowledge worth abstracting. But to a more junior developer who doesn't know the system well, that knowledge could be a good abstraction. Otherwise, they might have to hunt down the classes, remind themselves of how buttons work, and figure out the syntax for an onClick. Knowledge is relative and using it in a definition adds ambiguity.

Update: Xander left the following comment below. I think that article does a great job of explaining what "knowledge" should mean.

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...

single, unambiguous, authoritative representation

A "single" representation leaves a lot to be desired. From the view of a devops engineer, a single representation might be an entire application they need to deploy. To a frontend dev, that might be a component. And to a backend dev, that might be a method on a class or an API endpoint. Where does the line get drawn?

We also have the word "unambiguous" - but as I've just pointed out, the rest of this sentence defines more ambiguity. "Authoritative" makes sense - your DRY code should define exactly what it does and be true to that definition. However, that isn't explicitly confined to DRY code.

system

Finally, we have the world "system" - this gets back to the "single" statement we discussed a second ago. What is a "system"? In React, it might be a component or a Redux action/component/reducer. In containerized software, we could be talking about a whole pod or just a single instance.

At the end of the day, DRY all to often promotes pre-optimization, which is unnecessary and sometimes actually hurts your ability to write code. 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.

An alternative - Write Everything Twice (WET) programming

Instead, I propose WET programming. To me the definition would be:

You can ask yourself "Haven't I written this before?" two times, but never three.

With this definition the focus moves away from premature optimization and instead allows you to repeat similar code a couple times. It also shifts the focus to a more gut reaction. It allows you to make decisions based on the exact use case you are looking at. If you are building a web app, you probably want to abstract your buttons into a component, because you are going to be using a lot of them. But if there is a single page that has some special styling (maybe a pricing page?), then you don't need to worry too much about abstracting out the components on that page. In fact, under this system, if you needed a new page that was similar to that special page, you could just copy/paste and change the code you need. However, at the moment that that happens a third time, its time to spend a bit of time abstracting out the parts that can be abstracted.

I would also add this stipulation (to both WET and DRY programming):

You must comment your abstractions

Anytime you abstract something out you are reordering the map of your application. If you aren't commenting to discuss your reasons for abstracting, you are doing a disservice to your team (and your future self!).

What do you think? Does this track with how you develop?

Latest comments (93)

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.

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
 
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
 
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
 
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
 
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
 
bglick profile image
Brian Glick

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

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
 
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
 
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
 
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
 
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
 
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.