DEV Community

ivan.gavlik
ivan.gavlik

Posted on

Don't Repeat Yourself - practical tips at code level

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.

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

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.

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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

Here is main method

    public static void main(String[] args) {
        Predicate<Integer> even = num -> num % 2 == 0;
        NumberFiler.filter(Arrays.asList(1, 2, 3), even);
    }
Enter fullscreen mode Exit fullscreen mode

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.

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.

try {
    Connection con=ConnectionProvider.getCon();
    Statement st=con.createStatement();
    ResultSet rs=st.executeQuery("Select * from  Appointments where DoctorID=123");
    while(rs.next()) {
Enter fullscreen mode Exit fullscreen mode

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.

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;
    }

}
Enter fullscreen mode Exit fullscreen mode

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.

public <RESULT_ITEM> List<RESULT_ITEM> executeQuery(String query, Function<ResultSet, RESULT_ITEM> function) {
Enter fullscreen mode Exit fullscreen mode

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

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

Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)