DEV Community

ivan.gavlik
ivan.gavlik

Posted on

Transforming bad code by applying Single Responsibility principle, Don't repeat yourself, Decoupling ...

In this blog post, we are going to transform poorly designed code into a well-designed system that's easier to work with.

Poorly designed code can lead to frustration while working with it and can slow down progress on a project due to its difficulty in modification, repetition, and high coupling.

We'll explore how to overcome these challenges by applying principles:

  • Single responsibility principle
  • Don't repeat yourself
  • Decoupling

Let's roll up our sleeves and transform bad code into a good design system!

App E-HealthCare-Management-System

We are going to work on relatively small java console application, because it would take a lot of time to get into a big and complex app. It is still big enough to demonstrates mistakes and problems from real world and show how the mentioned principles can be applied.

Description

E-HealthCare-Management-System is a console java application for management of patients.

Patient can login, logout, view profile, view doctors, choose doctor, view appointments, book appointment, give feedback, view reports,pay online.

Doctor can login, logout, view profile, view appointments, attend to them.

Admin can login, logout, view profile, view patients list, add/remove doctors, view doctors list, see feedback given by patients, view reports.

Complete description is here

How to read this post

To fully understand the application, you should examine its source code on the GitHub repository. Here's what you need to know:

To gain insights into how the code evolved, review both branches. Start with the applicationBeforeRefactor branch to see how the code looked initially, and then move on to the master branch.

Additionally, while reading related posts or articles, periodically refer to the source code for a better understanding.

Initial situation

Note: take a look at applicationBeforeRefactor branch.

I next few section I am pointing out some of the problems with the code, then we see how they might be fixed by applying SRP, DRY and decoupling.

Package/folder and file structure

The current folder structure has all the classes within a single folder, making it difficult to identify which classes belong to which module or component. This can cause confusion during coding and make it challenging to locate and modify a particular class.

All classes are in one folder

Responsibility

    public void ShowPatientDetails(int id)/*This method all details of the patient*/
    {
        try {
            Connection con=ConnectionProvider.getCon();
            Statement st=con.createStatement();
            ResultSet rs=st.executeQuery("Select * from Patients where PatientID="+id);
            while(rs.next())
            { 
                System.out.println("PatientID:      "+rs.getInt(1));
                System.out.println("Name:           "+rs.getString(2)+" "+rs.getString(3));
                System.out.println("Blood-Group:    "+rs.getString(8));
                System.out.println("Address:        "+rs.getString(9));
                System.out.println("Contact-Number: "+rs.getString(5));
                System.out.print("\t********************\n");
            }
        }
        catch(Exception e)
        {
            System.out.println(e.getMessage());
        }
    } 
Enter fullscreen mode Exit fullscreen mode

Do you see what is wrong with this method ?

I believe you agree with me when I say that it does to much: create sql query, fetch data from DB, map it and then display it at UI (in this case UI is System.out ).

The same is with classes. Some of the examples are Main.java, Appointment.java... **

We see that each method and class has an overwhelming amount of responsibility, making it difficult to maintain and modify.

Additionally, the big amount of code per method and class can be challenging to analyze and comprehend, especially for someone who is not the original author.

Inconsistencies

    private String Doctor_Qualification;
    private int docFees;
    Scanner sc=new Scanner(System.in);
Enter fullscreen mode Exit fullscreen mode

Here it the example of variable naming, but in the project we have much more inconsistencies. They appear in:

  • writing comments
  • naming variables and methods
  • curly braces
  • indentation

Not only do inconsistencies like these create an obstacle to productivity, since understanding code takes longer and adapting to it becomes challenging, but they also contribute to a larger issue.

These inconsistencies can give the impression of a big mess in the code, which can lead to feelings of confusion. Of course, in this situation, you will probability ask your self if no one care, why should I ?

An author enjoys typing/coding.

    Connection con=ConnectionProvider.getCon();
    Statement st=con.createStatement();
    ResultSet rs=st.executeQuery("Select MAX(UserID) as NextUserID from Users where userType='Doctor'");
    // or
    st.executeUpdate("insert into Users values('"+DoctorID+"','"+"Doctor"+"','"+password+"')");
Enter fullscreen mode Exit fullscreen mode

In how many places this and similar code blocks are repeated ?

The author enjoys typing/coding and was not aware that he is duplicating code, also what is more important at some places he is duplicating knowledge.

In the long run, this practice makes code harder to maintain, and reuse across different parts of the software application. Additionally, increases the risk of introducing bugs in the duplicated code blocks.

Let's transform bad code

Note: Take a look at master branch.

Where to start

Note: This is not article on refactoring the goal is to see how can you benefit form SRC, DRY and decoupling

Code is not changed in one commit instead small changes are applied incrementally.

On your surprise in this step we are not touching the code. Idea is to define basic code style guidelines for the code, like:

  • Naming

  • Curly Braces style

  • Indentation

so that we enforce consistencies in it. Code style guidelines are in the README.md file

Second step, still we are not coding

I am starting with the big picture. Applying Single Responsibility principle at module/package level. Based on the functionally and current code, new packages are created and classes are reorganized. The idea is that each module/package has a single responsibility.

Applying Single responsibility principle at package level
Note: This is not final version, only the first version based on the knowledge I have

Next step decoupling DB access

Upon closer code inspection, we can see that now we have somethings that looks like modules, but they are highly coupled, and most of the methods have too many responsibilities.

To address this, we proceed with decoupling DB access from the rest of the code by implementing a simple, controlled change.

public final class Repository {

    private Connection connection;

    private Repository() {
        this.connection = ConnectionProviderDefault.getCon();
    }
    private Repository(Connection connection) {
        this.connection = connection;
    }

    private static Repository repository = new Repository();
    public static synchronized Repository getInstance() {
        if (repository == null) {
            repository = new Repository();
        }
        return repository;
    }

    @Deprecated
    public Connection getConnection() {
        return connection;
    }

    public boolean executeUpdate(String sql) {
      // check source code at git 
    }

    public <RESULT_ITEM> List<RESULT_ITEM> executeQuery(String query, Function<ResultSet, RESULT_ITEM> function) {
      // check source code at git 
   }
}
Enter fullscreen mode Exit fullscreen mode

Now we have single point of access to the DB with singleton class Repository.java, also much of the duplicate code is removed.
Here is the usage:

Repository.getInstance().executeQuery("SELECT * FROM Appointments", mapDbRowToAppointment);
Enter fullscreen mode Exit fullscreen mode

Did you notice how I am using technique known as passing behavior, more info here

Introduce business layer

After moving DB access to a separate class, the UI and business logic are still highly coupled. Let's move the business logic from the UI as well.

The idea is that each module/package should have at least one interface that defines business operations, as well as a default implementation of these interfaces. Examples include a Payment service, a Feedback service, and so forth.

Here is the example of PatientFeedback.java

public interface PatientFeedback {
    PatientFeedback DEFAULT_INSTANCE = new PatientFeedbackImpl(Repository.getInstance());
    List<Feedback> getFeedbacks();
    boolean addFeedback(Feedback feedback);
}
Enter fullscreen mode Exit fullscreen mode

Note that exposed interfaces give an access to its default implementation.

Finally UI

Although it is a console application, the principles for the UI are the same. Each module/package will have its own class named Portal that defines its view (following the Single Responsibility Principle). Then, they are all composed in the main method.

public static void main(String[] args) {
    Config.getInstance().loadConfig(args == null || args.length < 1 ? null : args[0]);

    Scanner sc = new Scanner(System.in);
    AdminPortal adminPortal = new AdminPortal();
    PatientPortal patientPortal = new PatientPortal();
    DoctorPortal doctorPortal = new DoctorPortal();
    PatientRegistrationPortal patientRegistrationPortal = new PatientRegistrationPortal();
Enter fullscreen mode Exit fullscreen mode

I want to reduce the re-implementation of UI components, specifically data tables in this app, by creating a reusable data table.

public final class TerminalTablePrinter {
    private TerminalTablePrinter() {}
    public static <ELEMENT> void printTable(List<String> header, List<ELEMENT> rows, Function<ELEMENT, List<String>> mapper) {
Enter fullscreen mode Exit fullscreen mode

In addition, I have created a Login component LoginTerminal.java and a component for inserting user data PersonTerminal.java, which can be reused throughout the code. These reusable UI components are placed in the terminalUtil package.

Are we done

There are lot of things in the code that should be improved:

  • exception handling
  • replace Statement with PreparedStatement (DB access)
  • naming convention at DB
  • validation layer
  • external party lib - validate credit card

Applying principles:

  • Single responsibility principle
  • Don't repeat yourself
  • Decoupling

we manage to transform poorly designed code into a well-designed system that's easier to work with, so I am ending this code transformation.

But here is the example how easily is to implement improvement when the code is well-designed.

Example replace Statement with PreparedStatement (DB access)

Because code for accessing DB is decouple from the rest of the application, not duplicated and has only one responsibility it is very easy to introduce change.

In the practice all changes in the code will happen in the Repository.java class

    public boolean executeUpdate(String sql) {
        this.checkConnection();
        try {
            PreparedStatement st= this.connection.prepareStatement(sql);
            st.executeUpdate(sql);
            return true;
        }catch(Exception e){
            e.printStackTrace();
            return false;
        }
        finally {
            try {
                this.connection.close();
            } catch (SQLException e) {
                throw new RepositoryConnectionCloseException();
            }
        }
   }
Enter fullscreen mode Exit fullscreen mode

For the practice. Feel free to implement this or some other improvement, share you code and we can discuss it.

Final words

Good code design is crucial for maintainability, scalability of software applications and developers productivity.

By applying key design principles like single responsibility principle, don't repeat yourself and decoupling to poorly designed code, we transformed inefficient, cluttered code into a well-designed system that's easier to work with.

We have seen how modules can be created with each having a single responsibility, business logic can be decoupled from the UI, and how reusable UI components can reduce code duplication. Incremental changes, following code style guidelines, and enforcing consistencies in naming, indentation, and curly braces can lead to better code quality.

By following these principles, we can reduce risks of introducing bugs, make our code easier to understand, and increase productivity while developing software applications.

Top comments (0)