Don't Repeat Yourself - practical tips at code level
Tags: java, opp, cleancode, dry
Published: 2023-05-16 20:01:21

Duplication is the root of all evil in software, because it is very easily to duplicate code which can lead to a lot of problems, such as code maintenance difficulties, increased bug potential, and decreased productivity.

Duplication is a so big problem that principle for it was created. Its name is Don’t repeat yourself or DRY. DRY premise is that by not repeating the same code throughout the system, developers can improve the quality of the code and make it easier to manage over time.

In this blog post am I sharing with you few practices you can apply to minimize code duplication.

## Here are a few tips to follow especially in Java:

- **Modularize** your code: Break your code into smaller, reusable functions or classes. This allows you to encapsulate specific functionality and use it in multiple places without duplicating the code. I wrote more about this in detail [here](https://dev.to/ivangavlik/decoupling-using-example-in-java-4cgb).

- **Use functions as parameters**: Instead of writing the same code with minor variations, create functions that can handle different scenarios by accepting parameters. This way, you can reuse the function with different inputs, reducing duplication.

- **Employ libraries and frameworks**: Java provides a vast collection of libraries and frameworks that offer pre-built functionality. Instead of reinventing the wheel, leverage these libraries to perform common tasks. This not only saves time but also reduces the need for duplicating code.

- **Abstract common code**: Identify patterns or common functionalities in your codebase and abstract them into reusable components. By creating abstractions, you can consolidate duplicated code into a single place and reference it wherever needed.

- **Separate concerns**: Keep your code organized and maintain a clear separation of concerns. Different parts of your codebase should handle specific tasks without overlapping responsibilities. This helps prevent duplication and promotes a more modular and maintainable codebase. I wrote more about this in detail [here](https://dev.to/ivangavlik/single-responsibility-principle-explained-with-example-in-java-3bb2).

In this post, we are going to see examples of DRY using functions as parameters and then see how to abstract common code.

## Example in Java using functions as parameters to achieve DRY

Using functions as parameters to methods is a common way to reduce code duplication and follow the DRY principle in programming. This technique is known as **passing behavior** Here's an example to illustrate how using function parameters can help us eliminate code duplication.

### Problem

Let's say we have two functions that perform the same operation on a list, the only difference is their logic for operating on individual elements in the list. 

```java
class NumberFilter {
    public static List<Integer> even(List<Integer> list) {
        List<Integer> evenList = new ArrayList<>();
        for (Integer i : list) {
            if (i % 2 == 0) {
                evenList.add(i);
            }
        }
        return evenList;
    }

    public static List<Integer> odd(List<Integer> list) {
        List<Integer> oddList = new ArrayList<>();
        for (Integer i : list) {
            if (!(i % 2 == 0)) {
                oddList.add(i);
            }
        }
        return oddList;
    }
}
```

### Solution

We are going to create the method filter() that accepts a list of integers and a function as parameter. The function is used to define the desired behavior for filtering the numbers (even, odd or any other filter). 

```java
class NumberFiler {
    public static List<Integer> filter(List<Integer> list, Predicate<Integer> predicate) {
        return list.stream()
                .filter(el ->  predicate.test(el))
                .collect(Collectors.toList());
    }
}
```

Here is main method
```java
    public static void main(String[] args) {
        Predicate<Integer> even = num -> num % 2 == 0;
        NumberFiler.filter(Arrays.asList(1, 2, 3), even);
    }
```

When a method accepts a **functional interface as a parameter**, we can define different behaviors and pass them to a method to perform different operations on the data, without repeating the same logic multiple times. At the end of this part I encourage you to do your research on Java functional interfaces API, here is the good starting [point](https://jenkov.com/tutorials/java-functional-programming/functional-interfaces.html).

**On the class level same could be achieved using a strategy pattern**.

## Example in Java How to abstract common code to achieve DRY

The Abstract common code doesn't mean we have to have an abstract class, it is just one of the ways how to do it. **Abstraction** means hiding irrelevant code to create general, reusable code that can be used in a variety of contexts, instead of repeating similar code for each specific context. Let's see this in practice.

### Problem
To save data in a DB we need to create a connection to the DB, prepare SQL statement, execute it and collect results, so we repeat these code blocks every time we need to save something which means that we have duplication.
```java
try {
    Connection con=ConnectionProvider.getCon();
    Statement st=con.createStatement();
    ResultSet rs=st.executeQuery("Select * from  Appointments where DoctorID=123");
    while(rs.next()) {
```
### Solution
After the first step (identifying duplicated code). Next step is to extract it with hiding irrelevant details (abstraction) and make it accessible from one single point (reusable). In Java, this can be a method or a class.  
```java
public final class Repository {
    private static Repository repository = new Repository();

    private Connection connection;

    private Repository() {
        this.connection = ConnectionProviderDefault.getCon();
    }
    
    public static Repository getInstance() {
        return repository;
    }

    public <RESULT_ITEM> List<RESULT_ITEM> executeQuery(String query, Function<ResultSet, RESULT_ITEM> function) {
        List result = new ArrayList();
        try {
            Statement st= this.connection.createStatement();
            ResultSet resultSet = st.executeQuery(query);
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            while (resultSet.next()) {
                result.add(function.apply(resultSet));
            }
        } catch (Exception ex) {
            throw new RepositoryException();
        } finally {
            try {
                this.connection.close();
            } catch (SQLException e) {
                throw new RepositoryConnectionCloseException();
            }
        }
        return result;
    }

}
```
To extract duplicate code, hide irrelevant details and have a singe point of access I decided to go with Java singleton class.


I am using one more rule to achieve DRY, can you find it, here is the little help. 

```java 
public <RESULT_ITEM> List<RESULT_ITEM> executeQuery(String query, Function<ResultSet, RESULT_ITEM> function) {
``` 
### When you should go for abstract class or interface ? 
If you have some variations, then  go for abstract class or interface.
We might get requirement to save data into file, so the solution could be like this
```java 
interface Repository {
   boolean save();
}

abstract class DBRepository implements Repository {
    private Connection connection; 
    // and other relevant DB things 
}

class FileRepository implements Repository {

    @Override
    public boolean save() {
        return false; // TODO implement
    }
}

``` 
## Challenges with DRY

Although the goal of this post is to share with best practices you can apply at the code level to implement the DRY principle. I have to point out that DRY (don’t repeat yourself) principle is wider and it is about the **handling knowledge** we don't want to duplicate the knowledge or intent also we do not want to express the same thing in two different places, especially in totally different ways.

The challenge we all face is that **Not all code duplication is knowledge duplication.** So in the first step when identifying duplication look for the places where the knowledge is duplicated and try to minimize it. 

If you need to violate the DRY try to localize the impact, so that violation is not exposed to the outside world.