concept

Connascence

also called Connascence of Meaning, Static Connascence

A graded vocabulary for coupling that ranks each dependency by how hard it is to change and how far apart the parties are, so boundaries can be argued with a ranking instead of adjectives.

cohesioncouplingmodularityparnascontracts

Two services agree that status code 7 means "refunded". Neither imports the other and the dependency graph shows nothing. Then someone adds code 8 for "partially refunded" and the other service classifies it as something else. No build broke, no test failed, and the defect surfaces as a reconciliation mismatch a fortnight later.

"Coupled" is the only word most teams have for that, and it is the same word they use for a function call — which the compiler repairs when the name changes. Meilir Page-Jones graded the difference in What Every Programmer Should Know About Object-Oriented Design (1995). Two components are connascent when a change to one requires a matching change to the other. Static forms are visible in the text of the code — name, type, argument position, meaning of a value, algorithm. Dynamic forms appear only at run time — execution order, timing, consistency of values, object identity.

Why it matters

Connascence of name is the weakest and safest form, because renaming breaks the build and the compiler performs your impact analysis for free. Connascence of meaning is much stronger and invisible, since agreement about a value leaves no artifact to break.

The second lever is distance. Page-Jones's rules of thumb are minimise total connascence, and keep what remains local. One magic number shared by two functions in a file is a smell, and the fix takes 10 minutes. Shared by two services in two repositories owned by two teams, it is an incident waiting for a new enum value, because the fix needs a coordinated release and those are measured in weeks.

That gives one sentence worth carrying into a review: the cost of a coupling is its strength multiplied by its distance. It also explains why extracting a service can make things worse: the extraction moved the connascence across a network boundary where nothing checks it.

Implementation patterns

  • Promote meaning to name. Replace the shared literal with an enum generated from one schema, so the build enforces it.
  • Convert position to name. Named parameters turn a swap of two same-typed arguments into a compile error.
  • Kill connascence of algorithm. Two services each implementing the same signing or rounding logic will drift. One library, or a conformance suite both must pass.
  • Set a boundary policy: across a service boundary only name and type, expressed in a schema; anything stronger needs a dated exception.

Industry example

Parnas's On the Criteria To Be Used in Decomposing Systems into Modules (CACM, December 1972) is the worked example, a quarter-century before the word. He decomposes one index-building program twice. The first follows processing steps, so every module knows the shared data structures — type and meaning crossing every boundary. The second hides each decision, so callers know only names.

Both work. Parnas's point is that the second absorbs a change of storage format inside one module while the first requires editing most of them, which is exactly what the grading measures.

Failure scenarios

  • A new enum value. One side adds it, the other falls through to a default. Nothing errors; a report is wrong.
  • Timestamp semantics. One service writes local time, the reader assumes UTC. Invisible for months, then wrong by an hour for half the year.
  • Rounding. Two services compute tax to 2 decimal places differently, so totals differ by cents and reconciliation costs more than the original work.

Trade-offs

Choose Gains Pays
Promote meaning to a shared schema The build catches drift A shared artifact with its own release and compatibility rules
Keep the literal local No indirection Invisible to tooling; it spreads silently
Duplicate the logic No dependency at all Connascence of algorithm drifting into a correctness bug

An estate that promotes everything ends up with a shared-schema monorepo whose release coordination is the new bottleneck. The judgement is which couplings are worth making mechanical, and the grading is what lets you rank them. A usable threshold: promoting meaning to a generated artifact costs roughly 2 to 5 engineer-days once, plus a compatibility rule thereafter, so it pays from about the third independent consumer, and it pays immediately at 1 consumer if that consumer sits behind a team boundary you cannot get a release from inside a week.

When not to use it

Do not bring the full taxonomy to a team that has not agreed boundaries matter; it reads as pedantry and the argument moves to terminology. Two phrases carry most of the value: "the compiler cannot see this" and "this crosses a team boundary". It is also the wrong tool for temporal and operational coupling, which are availability and delivery problems with their own vocabulary.

Interview question

Q: Two services both know a status of 7 means refunded. A third reads a settled_at timestamp one service writes in local time. Grade both couplings, say which you fix first, and what the fix costs.

What a strong answer covers: both are connascence of meaning and invisible to tooling. The timestamp goes first because it is already wrong rather than merely fragile, and its blast radius includes historical rows. The status code becomes connascence of name via a generated enum, at the cost of a versioned artifact and a compatibility rule. Distance matters: both would be minor if all three shipped from one repository.

Quick check

Quiz: Which is stronger — two modules sharing a function name, or two modules sharing the knowledge that 7 means refunded? The shared meaning, because a rename breaks the build and an agreed value leaves nothing to check.

Flashcard: Page-Jones's two rules of thumb? Minimise connascence overall and keep what remains local; cost is roughly strength times distance.