Reducing Coupling in Software Development: Strategies for Cleaner Code
Tags: coupling, cleancode, java, design
Published: 2023-07-02 20:26:15

Coupling refers to the degree of dependency between different components or modules within a codebase. High coupling can lead to code that is difficult to understand, modify, and maintain.  
In this article, we will explore various strategies to reduce coupling and promote cleaner code, but first:

## How to measure it ?

There is no definitive metric to measure coupling, here is one approach you can take. 

 **Dependency Analysis**: Analyze the dependencies between classes, modules, or packages in your codebase. Tools like static code analyzers or dependency visualization tools can help identify the relationships and dependencies between different components. High levels of interconnectivity and dependencies indicate a higher degree of coupling.

Tools:

- JDepend

- Structure101

- SonarQube (in addition to static code analysis, offers a module called "Dependency-Check" that visualizes dependencies between modules, packages, and classes.)

- Maven Dependency Plugin: By running the `dependency:tree` goal, you can obtain a tree-like structure that illustrates the dependency hierarchy of your project's modules and libraries.

## Solutions
- Tell, Don't Ask (TDA) Principle

- Avoid Method Call Chains

- Utilize Events

- Favor Interfaces over Inheritance

- Favor Composition over Inheritance

- External Configuration for Parameterizing the Application

### Tell, Don't Ask (TDA) Principle
One effective approach to reducing coupling is to follow the "Tell, Don't Ask" principle. Instead of exposing the internal state of an object for others to reason about, encapsulate the behavior within the object itself. By limiting external access to an object's state, we can reduce dependencies and make the codebase more maintainable.
```java
int age = person.getAge();
if (age >= 18) {
    // some java code
}

```  
Better approach
```java
if (person.isAdult()) {
    // some java code
}
```  

You will not always be able to apply this rule, but at least twice before you pull out data from the object to the outer world. 


### Avoid Method Call Chains
Chaining method calls can result in tight coupling between objects, making the code more brittle and harder to modify. 

```java
Energy.getInstance().getEnergyDropManager().getDrops();
```
It is fine that `Energy` has a direct link to `EnergyDropManager`, but think in terms of context of who is calling and everything it needs. If it needs `getDrops()` for 99% of calls, you should reconsider the purpose of this method and whether it might be wiser to move it to `Energy` instead.

```java
Energy.getInstance().getDrops();
```

Also, consider composing functions into pipelines or thinking of code as a series of nested transformations. This approach promotes a more modular and decoupled design.

```java
public class FunctionChainingExample {

    static Logger logger = Logger.getLogger(FunctionChainingExample.class.getName());

    private static Function<Integer, Integer> multiply = x -> x * 2;

    private static Function<Integer, Integer> add = x -> x + 2;

    private static Function<Integer, Unit> logOutput = x -> {
        logger.info("Data:" + x);
        return Unit.unit();
    };

    public static Unit execute(Integer input) {
        Function<Integer, Unit> pipeline = multiply
                                               .andThen(add)
                                               .andThen(logOutput);
        return pipeline.apply(input);
    }

    public static void main(String[] args) {
        execute(10);
    }

}
```

### Utilize Events
Applications that respond to events and adjust their behavior accordingly can be more flexible and adaptable in the real world.   

Options
- Finite State Machines, 

- Observer Pattern, 

- Publish/Subscribe, 

- Reactive Programming, 

- Streams

### Favor Interfaces over Inheritance
Inheritance can introduce strong coupling between classes, leading to code that is difficult to extend and modify. Instead, favor interfaces over inheritance. Interfaces allow for polymorphism without the tight coupling associated with inheritance. 

```java
public interface Renderer {
    void render();
}

public class CircleRenderer implements Renderer {
    public void render() {
        // Render a circle
        // ...
    }
}

public class SquareRenderer implements Renderer {
    public void render() {
        // Render a square
        // ...
    }
}

public class RendererApp {
    public static void main(String[] args) {
        Renderer circleRenderer = new CircleRenderer();
        circleRenderer.render();
        
        Renderer squareRenderer = new SquareRenderer();
        squareRenderer.render();
    }
}
```

### Favor Composition over Inheritance
Classes should be designed to be composed of other classes or modules rather than relying heavily on inheritance relationships.

```java
public interface Engine {
    void start();
}

public class GasEngine implements Engine {
    public void start() {
        // Start the gas engine
        // ...
    }
}

public class Car {
    private Engine engine;
    
    public Car(Engine engine) {
        this.engine = engine;
    }
    
    public void startEngine() {
        engine.start();
    }
}
```


### External Configuration for Parameterizing the Application
To reduce coupling and increase flexibility, consider parameterizing the application using external configuration. This can be achieved through static configuration files or as a service behind an API.   
By externalizing configuration, you can modify the behavior of the application without modifying the code itself, thereby reducing dependencies and improving the codebase's adaptability.

## Conclusion
Reducing coupling is essential for maintaining a clean and maintainable codebase. By following strategies such as "Tell, Don't Ask" principle, avoiding method call chains, utilizing events, favoring interfaces over inheritance, and externalizing configuration, you can effectively reduce coupling and achieve a more modular and flexible codebase. Embracing these practices will not only improve code maintainability but also improve the scalability and extensibility of your software project.