Coupling & Cohesion
The two metrics under every architecture decision: high cohesion within a component, low coupling between them - what afferent/efferent coupling and connascence tell you about changeability.
On this page
Underneath every architecture principle in this whole path - SOLID, DDD aggregates, hexagonal ports, module boundaries - sit two ancient metrics that decide how changeable a system is: coupling and cohesion. The timeless goal, coined by Larry Constantine in the 1960s and still the truest heuristic in software, is low coupling, high cohesion. Understand these two and you can evaluate almost any design decision on the spot.
Cohesion: do the parts belong together?
Cohesion measures how strongly the elements within a component belong together - how focused it is on a single purpose. High cohesion is good: a class or module does one well-defined job, and everything in it serves that job. This is literally the Single Responsibility Principle and the aggregate viewed as a metric.
- High cohesion - a
FeeCalculatorwhere every method computes fees. Focused, easy to understand, changes for one reason. - Low cohesion - the
LedgerManagergod class doing parsing, fees, persistence, and reporting. A grab-bag, changes for many unrelated reasons.
High cohesion makes a component easy to name, understand, test, and change - because it's about one thing.
Coupling: how entangled are the components?
Coupling measures how much one component depends on others - how entangled they are. Low (loose) coupling is good: components can change independently because they know little about each other. This is what Dependency Inversion, ports, and module boundaries all buy you.
- Tight coupling -
ordersreaches intobilling's internal classes and database tables. Change billing, break orders. - Loose coupling -
orderstalks tobillingonly through a small public API or events. Billing's internals change freely.
Coupling is the enemy of evolvability: the more tightly things are coupled, the wider the blast radius of any change, and the harder the system is to evolve. Nearly every technique in this path exists to reduce coupling.
Measuring coupling: afferent, efferent, instability
You can make coupling concrete. For a component:
- Efferent coupling (Ce) - the number of outgoing dependencies (things it depends on). High Ce means it breaks when many others change.
- Afferent coupling (Ca) - the number of incoming dependencies (things that depend on it). High Ca means changing it risks breaking many others.
- Instability (I) = Ce / (Ca + Ce) - ranges 0 to 1. Near 0 = stable (many depend on it, it depends on little) - hard and risky to change, so it should change rarely. Near 1 = unstable - depends on much, little depends on it - easy to change.
The design guidance: stable components should be abstract (interfaces, the domain), and volatile details should sit in unstable components. A concrete class with high afferent coupling (many things depend on it) is a change hazard - which is exactly why you depend on abstractions (Dependency Inversion), lowering the risk of depending on something stable.
Connascence: a finer lens
Connascence is a more precise vocabulary for coupling: two pieces of code are connascent if changing one requires changing the other to keep the system correct. It comes in degrees, from weak to strong:
- Connascence of Name (weak) - both refer to an entity by the same name; rename it in both. Easy.
- Connascence of Position (stronger) - both depend on the order of things (positional arguments); reorder and both must change. This is why the Builder pattern (named parameters) beats a long positional constructor.
- Connascence of Meaning / Algorithm (strong) - both must agree on a convention (a magic value, a hashing scheme). Fragile.
The rule of thumb: prefer weaker forms of connascence, and keep strong connascence local (within one class, where it's cheap) rather than spread across module boundaries (where it's a landmine). It's a sharper way to see why some coupling hurts more than other coupling.
You can't eliminate coupling - only manage it
Zero coupling means components that never interact - useless. The goal isn't no coupling; it's the RIGHT coupling: loose where components must interact, and via the weakest connascence possible. Beware also the opposite over-correction - so much indirection to 'decouple' that no one can follow the flow. As with everything in this path, it's a balance: enough decoupling that change stays local, not so much that simplicity dies.
Cohesion is how focused each department is: a Payroll team that only does payroll is highly cohesive - everyone shares a purpose, and you know exactly who to talk to. A 'Miscellaneous' department doing payroll, catering, and IT support is low-cohesion chaos. Coupling is how entangled the departments are: if Sales can only function by knowing the intimate internal procedures of Engineering, changing an Engineering process breaks Sales (tight coupling). Healthy companies keep departments focused (high cohesion) and interacting through clear, thin interfaces - 'file a ticket,' 'submit a request' - so each can reorganize internally without disrupting the others (loose coupling). Connascence is just noticing that 'we both use the same ticket form' (weak) is far safer than 'we both secretly rely on the fifth field meaning priority' (strong).
In ledger-legacy, a NotificationHelper class sends emails, formats currency, validates phone numbers, and writes audit logs. Meanwhile, the reporting module calls a method on it passing arguments in a specific order (sendAlert(userId, message, true, false, 2)) where the booleans and the 2 are magic flags. Analyze the cohesion of the class and the coupling/connascence of the reporting call, and prescribe fixes.
What is the timeless goal for coupling and cohesion, and why?
Key takeaways
- Coupling and cohesion are the two metrics under every architecture decision; the timeless goal is low coupling, high cohesion.
- High cohesion means a component is focused on one purpose (SRP as a metric); low coupling means components depend little on each other and change independently.
- Coupling is the enemy of evolvability - the tighter it is, the wider a change's blast radius; most techniques in this path exist to reduce it.
- Measure coupling via efferent (outgoing) and afferent (incoming) dependencies and instability I = Ce/(Ca+Ce); stable components should be abstract.
- Connascence grades coupling strength (name < position < meaning); prefer weaker forms and keep strong connascence local, not spread across boundaries.