Overview Coupling represents the degree of interdependence between software modules. While popular wisdom often dictates that "coupling is bad," the reality is more nuanced. You cannot build a functional system without some level of connectivity between components, especially when integrating with external APIs or libraries. The goal of a professional developer isn't to eliminate coupling entirely, but to manage it through intentional design. This guide explores how to identify toxic coupling patterns and refactor them into more maintainable structures. Prerequisites To follow this tutorial, you should have a solid grasp of Python fundamentals, including functions, classes, and type hinting. Familiarity with Object-Oriented Programming (OOP) principles and Data Classes will help you understand the refactoring choices. Key Libraries & Tools - **Python Standard Library**: Specifically the `dataclasses` module for cleaner object structures. - **Typing Module**: Utilizing `Protocol` for structural subtyping and abstraction. Code Walkthrough: Moving Beyond Global Coupling Global coupling occurs when functions directly access variables defined in the global scope. This makes testing and reuse nearly impossible because the function has hidden dependencies. ```python Bad: Global Coupling API_TOKEN = "secret_123" def make_request(endpoint: str): # Directly accessing global constants return f"Calling {endpoint} with {API_TOKEN}" ``` A better approach groups related data into a class. This encapsulates the configuration and allows you to instantiate multiple clients with different credentials. ```python from dataclasses import dataclass @dataclass class APIClient: token: str url: str def make_request(self, endpoint: str): return f"Calling {self.url}/{endpoint} with {self.token}" ``` Solving Stamp Coupling with Protocols Stamp coupling happens when you pass a large, complex object to a function that only needs a small portion of its data. This creates a brittle dependency on the entire object structure. Instead of passing the whole object, we can use a `Protocol` to define exactly what the function requires. ```python from typing import Protocol class Loggable(Protocol): transaction_id: int amount: float def log_transaction(item: Loggable): print(f"Processing ID: {item.transaction_id} for {item.amount}") ``` By using a protocol, the `log_transaction` function can now accept any object that has an ID and an amount, decoupling it from a specific `Transaction` class. Syntax Notes Python's **Structural Subtyping** (via `Protocol`) allows for "static duck typing." Unlike standard inheritance, a class does not need to explicitly inherit from a protocol to satisfy it; it simply needs to implement the required attributes or methods. This keeps your class hierarchies flat and flexible. Practical Examples These techniques are vital when building **SDK layers** or **Financial Systems**. In a banking app, separating "safe" withdrawals (which check balances) from "unsafe" internal transfers (which might allow temporary negatives) prevents logic duplication while maintaining clear boundaries. Tips & Gotchas - **Avoid "God Classes"**: When refactoring content coupling, don't just shove every function into a single class. This leads to bloated objects that are hard to maintain. - **Naming Matters**: If you must expose an internal modification method, name it explicitly (e.g., `withdraw_unsafe`) to warn future developers of the risks. - **AI Limitations**: While tools like GitHub Copilot generate code quickly, they often struggle with high-level architectural reasoning. Design patterns remain a human-led discipline.
Coupling
Concepts
- Apr 4, 2025
- Sep 2, 2022
- Jan 22, 2021