13

Coupling / Cohesion

#
  • heuristic
  • established
  • fragment

The only pair of design metrics that can actually be measured (afferent/efferent coupling, instability). In practice: dependency direction is checked in CI, not in the reviewer's head canon.

Both words come from Stevens, Myers and Constantine, “Structured Design” (IBM Systems Journal, 1974), which ranked kinds of coupling and cohesion and argued for low coupling between modules and high cohesion within them. The countable metrics are later: Robert C. Martin’s package metrics (afferent and efferent coupling, instability, abstractness) and the Stable Dependencies Principle — depend in the direction of stability.

What exactly is counted: Ca (afferent) — how many modules depend on you; Ce (efferent) — how many you depend on; instability I = Ce / (Ca + Ce). I = 0 — everyone depends on you and you depend on no one: expensive to change, but nobody breaks you. I = 1 — the reverse. The value is not in the numbers themselves but in the direction: the stable must not depend on the unstable, otherwise a change in the most volatile module propagates up the graph to all its readers. Cohesion is not measured with the same ruler — it is the question “does the module's content change for a single reason” (1), and it remains a judgment call.

Code example (fragment)
Java · ArchUnitfragment
// dependency rule as a test; definedBy matches on PACKAGE NAMES —
// ArchUnit reads bytecode, so the naming convention is this test's contract
@ArchTest
static final ArchRule layering = layeredArchitecture().consideringAllDependencies()
    .layer("domain").definedBy("..domain..")
    .layer("infra").definedBy("..infra..")
    .whereLayer("domain").mayNotAccessAnyLayer(); // domain knows nothing about infra

Go — depguard / go list -deps in CI. Rust — crates in a workspace (dependency graph = Cargo.toml, cycles impossible). Python — import-linter with a layers contract.

What the import graph does not see. Two services with no code dependency can be tightly coupled through a shared database table, a message format, a cache key layout or an assumption about ordering: change one column and both break, and no ArchUnit rule notices. Meilir Page-Jones’s connascence names these hidden kinds — of name, type, meaning, timing — and ranks them by how far apart the coupled pieces live. The practical rule: a dependency that crosses a team or a deploy unit must be visible as a contract (18, 74), because the graph will not show it. Cohesion, in turn, trades against DRY (43): pulling shared code into a common module raises coupling to it, and two copies that change for different reasons are more cohesive than one shared helper.

Last technical review: 2026-08-04 · Reference · 112 principles