Coupling measures how much one component depends on another. Two components are tightly coupled when a change in one requires a change in the other. They are loosely coupled when each can change independently. Low coupling is the goal at component boundaries.
Cohesion measures how well the elements within a component belong together. A component with high cohesion performs one clearly-defined function. Its elements are related by purpose. Low cohesion — a class that manages users, sends emails, and writes to the audit log — signals that the component covers too many responsibilities and will be modified for too many different reasons.
The target state is always: high cohesion within components, low coupling between components. These goals are complementary but not free. Achieving low coupling requires deciding what belongs together (cohesion) and enforcing boundaries around those clusters.
Coupling types, ordered from weakest to strongest:
Data coupling — components communicate by passing
only the data they need. A function receives amount: Money
and returns Receipt. The caller and callee are minimally
entangled.
Stamp coupling — components pass whole objects when
they only need a field. processPayment(order: Order) when
the function only uses order.amount. The callee now depends
on the entire Order type.
Control coupling — one component tells another what
to do via a flag. process(data, mode="strict"). The caller
has knowledge of the callee’s internal branching logic.
Common coupling — multiple components share global state. A global configuration dictionary, a module-level registry, a class variable. All components reading that state are coupled to each other through it.
Content coupling — one component directly accesses the internals of another. Service B reads Service A’s database table. One class modifies another’s private fields. This is the strongest and most damaging form.
The fintech example in the introduction is content coupling: Service B accessed Service A’s database directly, giving Service B knowledge of Service A’s internal storage format.
Two code smells signal coupling problems in practice. Feature envy
appears when a method in class A spends most of its logic reading and
transforming fields from class B. The method belongs in B; it was placed
in A by accident or convenience. Shotgun surgery appears when a single
change — a new field on Order, a new payment status —
requires modifications in ten different places. The change propagates
because the concept is spread across many components instead of being
owned by one.