Mastering Code Quality: A Deep Dive into Cohesion and Coupling in Python

Overview

Software design often feels like a balancing act between making things work and making them sustainable. Two critical metrics for measuring this balance are

and
Coupling
. Cohesion refers to how closely the responsibilities within a single class or function relate to one another. High cohesion implies a focused, single-purpose unit. Coupling, conversely, measures the degree of interdependence between different modules. While some interaction is necessary, high coupling creates a fragile "house of cards" where a single change can trigger cascading failures across the system. This tutorial refactors a disorganized vehicle registration system into a robust, modular architecture by applying these principles.

Prerequisites

To follow this guide, you should have a solid grasp of

fundamentals, particularly Object-Oriented Programming (OOP). You should be familiar with defining classes, using self, and understanding basic data structures like dictionaries. No advanced libraries are required, though a basic understanding of type hinting will help in reading the refactored code.

Key Libraries & Tools

  • Python Standard Library: The core logic uses standard Python features. Specifically, the math library is often cited as a gold standard for cohesion (e.g., the cos() function).
  • Type Hinting (typing): Used to define data structures clearly, making the code more readable and easier to debug.
  • GRASP Principles: The
    GRASP
    provide the conceptual framework, specifically the Information Expert pattern.

Code Walkthrough

The Problem: Low Cohesion and High Coupling

The initial code suffered from a "God Method" syndrome. The register_vehicle function was responsible for generating IDs, calculating prices, determining tax rates based on brand names, and printing results. It knew too much about the internal workings of other classes.

Step 1: Defining the Information Expert

According to

, we should assign responsibility to the class that has the information necessary to fulfill it. We split the data into two distinct classes: VehicleInfo (for brand-level data) and Vehicle (for specific instances).

class VehicleInfo:
    def __init__(self, brand: str, electric: bool, catalog_price: int):
        self.brand = brand
        self.electric = electric
        self.catalog_price = catalog_price

class Vehicle:
    def __init__(self, id: str, license_plate: str, info: VehicleInfo):
        self.id = id
        self.license_plate = license_plate
        self.info = info

Step 2: Improving Cohesion through Focused Methods

We moved the tax calculation logic into VehicleInfo because that class owns the catalog_price and electric status. This is high cohesion: the class manages its own data logic.

def compute_tax(self) -> float:
    tax_percentage = 0.05
    if self.electric:
        tax_percentage = 0.02
    return tax_percentage * self.catalog_price

Step 3: Reducing Coupling in the Registry

Instead of the application logic manually stitching together IDs and licenses, we moved that into a VehicleRegistry. The main application now only needs to know about one method: create_vehicle.

class VehicleRegistry:
    def create_vehicle(self, brand: str) -> Vehicle:
        vehicle_id = self.generate_vehicle_id(12)
        license_plate = self.generate_vehicle_license(vehicle_id)
        brand_info = self.vehicle_info[brand]
        return Vehicle(vehicle_id, license_plate, brand_info)

Syntax Notes

This tutorial utilizes f-strings (string interpolation) for clean output formatting. It also relies heavily on Type Hinting, which doesn't change runtime behavior in Python but serves as critical documentation for the developer. By using self within the Vehicle class to call methods from the linked VehicleInfo object, we demonstrate composition, a key OOP pattern that favors flexibility over rigid inheritance.

Practical Examples

These principles apply whenever you face "spaghetti code." Real-world applications include:

  • E-commerce Systems: Separating Product (specs) from LineItem (quantity in a specific cart).
  • Game Development: Moving damage calculation into a Weapon class rather than the global Player controller.
  • API Clients: Ensuring a Request builder doesn't also handle the complex Response parsing logic.

Tips & Gotchas

  • The Zero-Coupling Myth: You can never achieve zero coupling; objects must interact. Aim for loose coupling where objects interact through stable interfaces rather than internal implementation details.
  • Naming is a Signal: If your function name contains the word "and" (e.g., calculate_and_print), it likely has low cohesion and should be split.
  • Avoid String Matching: Initially, tax was calculated by checking if a string was "Tesla." Refactoring this to a boolean electric attribute makes the system much easier to extend without modifying the logic for every new brand.
4 min read