May 29, 2024
One of the common problems in development projects is code redundancy. Many developers, especially juniors (due to lack of experience), tend to duplicate methods or blocks of code. This is something CTOs fight against.
Redundancy is one of the reasons development paradigms (or patterns) are created. This is the case with the DRY principle: Don't repeat yourself.
Want to know more about DRY? We explain everything in this article!
As we said in the introduction, many development projects contain unnecessary repetitions of code fragments. And while this is not always a problem, it can become one, especially during the maintenance and evolution phases of the project.
Typically, if we need to make a change (for bug fixing or new feature development) to a block of code, and it is repeated in three places in the project, we will have to change it three times. This can become an issue if we forget to change it in one particular place; this could, for example, cause cascading bugs.
But, the easiest way to understand is to see a concrete example.
Let's take a simple example to explain the problem with JavaScript. We have a web application with two login forms: one on the home page, the other on the specific login page.
The developer chose to create two separate functions to manage the two forms independently. This choice may be coherent, and the implementation is as follows:
// home page controller
loginFromHomePage = (email: string, password: string) => {
const connectRequest = "select * from user where user.email = " + email + " AND user.password = " + password;
const connectionResponse = UserConnector.executeRequest(connectRequest);
}
// login page controller
loginFromLoginPage = (email: string, password: string) => {
const connectRequest = "select * from user where user.email = " + email + " AND user.password = " + password;
const connectionResponse = UserConnector.executeRequest(connectRequest);
}
There are two functions, each constructing a SQL connection query to query the database.
Now imagine that, for database rationalization reasons, we decide to prefix all columns with the first three letters of the table name:
We will need to change the code in all places where queries are constructed on the user table. In our case, we will need to change it in two places. The risk is that the developer forgets one of the two functions, and the login no longer works everywhere...
We should have constructed the query in one place. We will see how to solve this specific problem later in this article.
Of course, this case is extremely simple. When we talk about applying DRY, it is more often at the level of inheritance and abstraction of classes.
Find more development tools on Indie Dev Tools.
As we said, where the DRY paradigm makes the most sense is when talking about higher-level code: inheritance, abstractions, interfaces, etc.
This means, for example, that instead of repeating the same attributes or functions in several classes, it is better to create a "super-class" containing these attributes and functions, and manage the rest through inheritance. Of course, this must make sense logically.
Simplifying the code to the maximum does not help if it complicates understanding.
To take our example of login functions from the previous section, let's see how we could have improved these methods by applying the DRY principle.
Instead of having these two functions executing the same code, having a single one containing most of the business logic would make more sense. For example, we could have:
// home page controller
loginFromHomePage = (email: string, password: string) => {
userService.LogUserIn(email, password);
}
// login page controller
loginFromLoginPage = (email: string, password: string) => {
userService.LogUserIn(email, password);
}
// Main login function in the service
LogUserIn = (email: string, password: string) => {
const connectRequest = "select * from user where user.email = " + email + " AND user.password = " + password;
const connectionResponse = UserConnector.executeRequest(connectRequest);
}
Thus, if we come to modify the SQL tables, we will only need to change the code in one place. This will avoid omissions and therefore potential bugs.
However, be careful: DRY should not be implemented all the time!
This may seem counterintuitive, as we have just said and seen that it allows for cleaner and more maintainable code with fewer potential bugs.
However, it is not that simple, and the DRY principle should not be pushed to its extreme.
In our previous example, we could have simply deleted the two login functions and called only the new one. This would seem logical, and doing it this way would make sense. The DRY principle would thus be applied and respected to the end.
However, imagine if the processing had to differ depending on the origin of the connection? If the interface return or URL redirection had to be different? In this case, it is better not to simplify to the extreme and have slightly decoupled code.
When developing a feature, it is important to try to think about the future, rather than wanting to simplify everything to the extreme when writing the code. This means that the don't repeat yourself should be applied during the code refactoring phases rather than in its writing phase.
Even though, obviously, a large part of the code can be simplified from the start.
Another possible origin of the overuse of DRY is internally defined rules, especially by CTOs. Many of them often want, at all costs, to have ultra-rationalized code from the beginning of a project. While this is good, it is better to keep some flexibility, which will avoid going back later.
The wrong reasons for using the DRY principle mentioned above can lead to the Swiss Army knife effect. We bring together several features in one place to create a super container of features; which is in itself a good idea.
But what happens if we need to add a new tool to the knife, which does not fit in and requires rethinking its entire construction? Or modify one of the existing tools to optimize its functionality, thus moving all the others?
In this case, isn't it better to separate the tools and have a toolbox rather than everything in one container?
In short, and once again: it is better to apply the DRY principle during code refactoring rather than during its development.
A typical example: you have developed a front-end component, a calendar. You have integrated other elements into it: time management, for example. Now you are asked to manage time zones in this same calendar. This will impact not only time management but probably the rest of the component, whether in terms of UX/UI or code.
Using two separate components would have allowed better maintenance.
Opposite to DRY, we have WET! Meaning Write Everything Twice, this is not a paradigm, but rather the bad practice that DRY tries to combat.
Bad practice is relative! As we said, it is not necessarily bad to write the same code or similar blocks of code multiple times! It's all about readability, cleanliness, and maintainability.
As we have seen, the DRY principle – don't repeat yourself – is a programming paradigm aimed at helping programmers simplify their code. This has the effect of making the program simpler (to read, understand, and maintain), but also prevents bugs caused by code duplication.
However, DRY should not be applied everywhere! As we have said, there are exceptions, and they should be respected.
There are also other principles than this pattern that can be applied to make the code clearer.
You might also want to read: Why Your Website’s Performance Matters for SEO (and How to Improve It)
Written by Alexandre Grisey