DRY (Don’t Repeat Yourself)

Loïc Boset - Apr 23 '23 - - Dev Community

TL;DR

  • DRY = Don’t Repeat Yourself
  • make sure you don’t duplicate code or knowledge
  • if you identify duplicate code, try to abstract it and make it reusable
  • keep your code modular (tackling one specific task) and decoupled (minimum number of dependencies)

"The code you write today will be somebody's legacy code tomorrow.” - The Pragmatic Programmer

Intro

Software engineering principles are guidelines that help software developers write good code.

Good code is efficient, maintainable, and scalable.

In this series of articles, we will cover some of the most important principles that we need to apply when writing code.

Today, we will be discussing the DRY (don’t repeat yourself) principle. Where does it come from? What does it mean? How can we apply it?

Origin

The DRY principle has been formulated by Andy Hunt and Dave Thomas in their book The Pragmatic Programmer, in 1991.

This book is a classic in the software development field and is a recommended-read for any developer.

Meaning

The DRY principle states that “Every piece of knowledge must have a single unambiguous, authoritative representation within a system”.

Unambiguous means that your code should be clear and not open to multiple interpretations.

Authoritative means that your code serves as the definitive reference for a particular functionality. In other words, you should not have 2 blocks of code doing the same thing.

By following the DRY principle, you ensure that your code is clear and free of duplication.

Application

There are a few rules you can follow to make sure your code is DRY:

1. Identify Code Duplications

When writing code, before creating a new function, a new component or any new piece of logic, make sure there is nothing similar already present in the codebase.

Now, imagine that in your codebase you need to transform milliseconds to minutes multiple times, at different places. You could have something like that:

// Component A
const functionA = () => {
    // some logic here ...

    const intervalInMilliseconds = 60_000;
    const intervalInMinutes = Math.floor(intervalInMilliseconds / 60_000);

    // some logic there ...
}:
Enter fullscreen mode Exit fullscreen mode
// Component B
const functionB = () => {
    // some logic here ...

    const intervalInMilliseconds = 120_000;
    const intervalInMinutes = Math.floor(intervalInMilliseconds / 60_000);

    // some logic there ...
}:
Enter fullscreen mode Exit fullscreen mode

You realise that you have duplicated logic. What can you do?

2. Make Your Code Reusable

If you identify duplicated logic, think about how you can make it generic and reusable in all the places it is needed. Create an abstraction (such as a function) that encapsulates that logic and reuse it wherever needed.

When you do that, you are creating a single authoritative representation of that logic.

Taking back our previous example, here’s what you can do:

// utils/millisecondsToMinutes.js

const millisecondsToMinutes = (ms) => {
  const minutes = Math.floor(ms / 60_000);
  return minutes;
}

export default millisToMinutes;
Enter fullscreen mode Exit fullscreen mode
// utils/index.js
import millisToMinutes from './millisToMinutes';
// other utils functions here ...

export { 
    millisToMinutes,
    // other functions exported here ...
};
Enter fullscreen mode Exit fullscreen mode
// Component A
import millisecondsToMinutes from 'utils'

const functionA = () => {
    // some logic here ...

    const intervalInMilliseconds = 60_000;
    const intervalInMinutes = millisecondsToMinutes(intervalInMilliseconds);

    // some logic there ...
}:
Enter fullscreen mode Exit fullscreen mode
// Component B
import millisecondsToMinutes from 'utils'

const functionB = () => {
    // some logic here ...

    const intervalInMilliseconds = 120_000;
    const intervalInMinutes = millisecondsToMinutes(intervalInMilliseconds);

    // some logic there ...
}:
Enter fullscreen mode Exit fullscreen mode

3. Keep Your Code Modular And Decoupled

Modularity means that your code is reduced to small modules, components, or functionalities, responsible for a specific task, such as you can easily reuse it.

Decoupling means that you have reduced to a minimum the dependencies of your logic, making it easier to modify or replace one module without affecting others.

Here’s an example of a non-modular and tightly coupled piece of code:

// Non-modular code with tight coupling
const calculateNetSalary = (baseSalary) => {
  const taxRate = 0.4; // Hardcoded tax rate
    const deduction = 100; // Hardcoded fixed deduction
  const taxAmount = baseSalary * taxRate;
  const netSalary baseSalary - taxAmount - 100;
    sendSalarySlipByEmail(netSalary);
    return netSalary;
}
Enter fullscreen mode Exit fullscreen mode

In the above example, the function directly uses hardcoded values for tax rate and deduction. This makes the function dependent on these specific values and difficult to modify without changing the function's implementation. If these values need to be changed, the function would need to be updated directly, which can lead to maintenance issues and make the code less flexible.

The function is also not quite modular as it does more than one thing. In addition to calculating the net salary, it sends the payslip by email with the sendSalarySlipByEmail function integrated within it.

Here’s a more modular and less coupled version:

const calculateNetSalary = (baseSalary, taxRate, deduction) => {
  const taxAmount = baseSalary * taxRate;
  const netSalary = baseSalary - taxAmount - deduction;
  return netSalary;
}

const netSalary = calculateNetSalary(5000, 0.4, 100);
sendSalarySlipByEmail(netSalary);

Enter fullscreen mode Exit fullscreen mode

4. Do Code Reviews

If you work with other people on the same codebase, ask someone to review your code, and review other developer’s code. Fresh perspectives can often identify smelly code (such as duplication) that may not be apparent to the original author.

Conclusion

Applying the DRY principle involves identifying and eliminating code duplication, making your code reusable, abstracting common functionalities, and keeping your code modular and decoupled.

Regular refactoring and code reviews can help in maintaining adherence to the DRY principle in day-to-day coding practices.

By following the DRY principle, you will create more efficient, maintainable, and high-quality softwares.

. . . . . . . . . . . . . .