DEV Community

Arturo Lucas
Arturo Lucas

Posted on

Coupling & Cohesion

Introduction

Coupling and Cohesion are two classic concepts in software engineering. In this article I explore ways to apply this concepts at multiple levels of any project.

Unit

First of all, is important to define what a Unit is in the context of this article:

  • a unit is an element that compose a complex structure.
  • a unit can be classified as:
    • methods
    • classes
    • packages
    • services

Coupling

what is?

  • coupling is the degree of interdependency between units.
  • coupling is the degree of how a unit uses other units directly.

how this affects our projects?

a highly coupled project can lead to problems like:

  • when a unit needs to be modified you must, as an obligation, to change the other units.
  • code that is hard to change and optimize.

how can I deal with highly coupled units?

there will always be a coupling degree in any project, the objective is to reduce that number.

in methods

in the example below registry methods are deeply coupled to the register_vehicle method.

class Application:
    def register_vehicle(self, brand: string):
        registry = VehicleRegistry()
        vehicle_id = registry.generate_vehicle_id(12)
        license_plate = registry.generate_vehicle_license(vehicle_id)
        catalogue_price = registry.calculate_price(license_plate, brand, vehicle_id)
        ...
Enter fullscreen mode Exit fullscreen mode

to reduce coupling I moved all that business logic inside the registry service.

class Application:
    def register_vehicle(self, brand: string):
        vehicle_repository = VehicleRepository()
        registry_service = VehicleRegistryService(vehicle_repository)
        vehicle = registry_service.register_vehicle(brand)
        ...
Enter fullscreen mode Exit fullscreen mode

in classes

the class CourierController has a concrete class Whatsapp as a dependency.

export default class CourierController {
  constructor (private readonly courier: Whatsapp) {
  }
}
Enter fullscreen mode Exit fullscreen mode

to reduce coupling I created an interface or abstract class to be used by CourierController.

export default class CourierController {
  constructor (private readonly courier: Courier) {
  }
}
Enter fullscreen mode Exit fullscreen mode

in packages

many MVC frameworks use a coupled package structure. the controller package use views, and the views package use models.

Image description

to reduce coupling I applied vertical slicing and structured the project based on business entities.

Image description

in services

when a project grows is common to have somethings like the image below, services are consumed by other services and this can produce performance issues in the architecture.

Image description

to reduce coupling between services, a queue can be used, this allows to process expensive operation asynchronously and decouple the architecture.

Image description

Cohesion

what is?

  • cohesion is the degree of relation of the elements inside a unit.
  • cohesion is the degree is which every element inside a unit are directed towards perform a single task.

how this affects our projects?

a poorly cohesive project can lead to problems like:

  • having many responsibilities for the same unit.
  • code that is hard to test and maintain.

how can I deal with poorly cohesive units?

in methods

in the code below the internal elements of register_vehicle have multiple responsibilities.

class Application:
    def register_vehicle(self, brand: string):
        # First responsability
        registry = VehicleRegistry()
        vehicle_id = registry.generate_vehicle_id(12)
        license_plate = registry.generate_vehicle_license(vehicle_id)

        # Second responsability
        catalogue_price = 0
        if brand == "Tesla Model 3":
            catalogue_price = 60000
        elif brand == "Volkswagen ID3":
            catalogue_price = 35000
        elif brand == "BMW 5":
            catalogue_price = 45000

        tax_percentage = 0.05
        if brand == "Tesla Model 3" or brand == "Volkswagen ID3":
            tax_percentage = 0.02

        payable_tax = tax_percentage * catalogue_price

        # Third responsability
        print("Registration complete. Vehicle information:")
        print(f"Brand: {brand}")
        ...

Enter fullscreen mode Exit fullscreen mode

to improve cohesion I moved all that business logic inside the registry service. Now the elements inside the register_vehicle method are towards the same goal.

class Application:
    def register_vehicle(self, brand: string):
        vehicle_repository = VehicleRepository()
        registry_service = VehicleRegistryService(vehicle_repository)
        vehicle = registry_service.register_vehicle(brand)
        vehicle.print()

Enter fullscreen mode Exit fullscreen mode

in classes

in the example below the class Utils have multiple responsibilities. This class will grow without any business role or good practice and will became a god class

Image description

to address this and improve cohesion, I relocated each of those methods to their respective classes, aligning them with their corresponding responsibilities.

Image description

in packages

in the example below a basic hexagonal architecture structure is shown. However, the elements within each package lack cohesion as they serve different purposes.

Image description

to solve this, screaming architecture can be applied.

Image description

in services

in the example below two services Sales and Invoicing are shown. Both services have multiple responsibilities apart from its main ones, such as Auth, Email sending, AWS access, etc. This indicates a lack of cohesion within the architecture.

Image description

to solve this I grouped the admin services and the external services into their proper units and then I referenced those units from the Sales and Invoicing services.

Image description

References

Top comments (0)