rgoussu@goussu: ~/library/applicative-architecture
~/library/applicative-architecture cat coupling-and-cohesion.md

Coupling & cohesion

# The two forces every architecture balances — minimize how much modules know about each other, maximize how much each module belongs together.

Conceptsaved 2026-08-08updated 2026-08-20 #coupling#cohesion#architecture#design#modularity

Overview

Coupling measures how much one module depends on the internals, timing, or availability of another; cohesion measures how strongly the elements inside a module belong together. They are the oldest pair of forces in software design (Constantine & Yourdon, structured design, 1970s) and still the vocabulary behind nearly every architectural argument: good boundaries are the ones that keep cohesion high inside and coupling low across. Almost every modern pattern — layering, hexagonal ports, bounded contexts, microservices — is a strategy for managing this trade-off, not an escape from it.

Key points

  • Low coupling ≠ no coupling. Modules must connect to be useful; the goal is depending on stable, narrow, explicit contracts rather than on internals. Afferent/efferent coupling and the stable-dependencies principle give it a measurable form.
  • Kinds of coupling, worst to best (structured-design lineage): content (reaching into internals) → common (shared global state) → control (passing flags that steer the callee) → stamp (passing whole structures for one field) → data (passing just what's needed). Modern additions: temporal coupling (must run in order / at the same time) and deployment coupling (must ship together) — the two that distributed systems make expensive.
  • Kinds of cohesion, worst to best: coincidental (utility grab-bags) → logical → temporal → procedural → communicational → sequential → functional (one reason to exist). "God classes" and utils/ folders are cohesion failures before they are size problems.
  • Cohesion decides where code lives; coupling decides how it talks. A boundary drawn through a cohesive cluster converts cheap in-process calls into expensive cross-boundary coupling — the classic distributed-monolith failure.
  • Connascence (Meilir Page-Jones) refines the vocabulary: two elements are connascent when changing one forces changing the other; prefer weaker (name, type) over stronger (order, timing, values), and keep strong connascence local.
  • Heuristics: put things that change together, together (common closure); depend in the direction of stability; make coupling explicit (interfaces, events, contracts) so it can be seen and tested; measure change-coupling in version history, not just imports.

Practice

  • Connascence hunt (source) — pick one module and classify every connascence instance you find, then refactor the strongest (position, timing, values) down toward the weakest (name, type); recognition drill for invisible coupling.
  • Trip Service kata (source) — the code can't even be tested until you break its coupling to statics and singletons; the best hands-on lesson on why coupling direction matters.
  • Racing Car katas (source) — each small exercise is rigged so one coupling violation blocks testability; fix the coupling to get the test in.
  • ArchUnit fitness tests (source) — encode your intended dependency directions as executable tests on a real codebase and watch what fails: coupling made visible and enforceable.
  • Change-coupling archaeology (source) — mine a repo's git history (code-maat or CodeScene) for files that always change together but live apart — cohesion failures no import graph will show; the exercises from Tornhill's Your Code as a Crime Scene.
  • Gilded Rose — legacy rescue (exercise) — the flagship cohesion workout: extract a design from one god function, under tests.
  • Birthday Greetings — hexagonal extraction (exercise) — the coupling workout at architecture scale: start from a deliberately coupled script and cut the boundary — cohesive pure core behind ports, I/O pushed into adapters.

Related