100%

الدرس 2 من 2

Cohesion, and a Precise Vocabulary for Coupling

Cohesion is what changes together, not what sounds related. Connascence gives you three dials — strength, locality and degree — for describing coupling accurately enough to act on.

قراءة 10 دقيقة

Cohesion is the other half of the pair, and it is the one teams get wrong more quietly. High cohesion means the things inside a module belong together — but "belong together" has a specific meaning that is not "sound similar". Things belong together when they change together.

LevelWhat binds the partsWhat you observe
Functional (best)Everything contributes to one well-defined taskChanges touch the whole module or none of it
Sequential / communicationalThe parts pass data along one flow or work on the same dataChanges cluster, with occasional isolated edits
Procedural / temporalThe parts run at the same time — start-up, shutdown, nightlyUnrelated edits arrive together only because of the schedule
LogicalThe parts are the same *category* of thing: all validators, all helpersEvery change touches one function and never its neighbours
Coincidental (worst)Nothing; the file is where things were putA `utils.ts` that half the codebase imports and nobody understands
Cohesion ranked, with the pattern each level produces in a real repository.

Connascence: saying exactly how two things are coupled

Connascence, a vocabulary introduced by Meilir Page-Jones, is the most practical tool available for talking about coupling precisely. Two pieces of code are connascent if changing one requires changing the other to keep the system correct. What makes it useful is that it gives you three independent dials rather than one vague quantity.

  • **Strength** — how easy the coupling is to find and fix. Connascence of *name* (both sides use the same function name) is weak: a compiler finds every occurrence. Connascence of *meaning* (both sides know that status `3` means "cancelled") is strong, because nothing will tell you when one side changes its mind.
  • **Locality** — how far apart the two things are. Strong connascence between two adjacent lines is fine. The same strength between two services in different repositories is a defect waiting to happen. Strength is tolerable in proportion to closeness.
  • **Degree** — how many places participate. Two things agreeing on a magic number is a small problem; forty things agreeing on it is a migration.
The same rule expressed with strong connascence and then weakened, without changing behaviour.typescript
// Connascence of meaning + of position: the caller must know that 3 means
// cancelled and that the second argument is the reason. Both are invisible.
recordStatus(order.id, 3, 'user_request');

// Weakened to connascence of name, which the compiler can check everywhere.
recordStatus({ orderId: order.id, status: OrderStatus.Cancelled, reason: CancelReason.UserRequest });

// Degree also drops: the meaning of "cancelled" now lives in one enum rather
// than in every call site that remembered the number 3.
export const OrderStatus = { Cancelled: 'CANCELLED', Shipped: 'SHIPPED' } as const;

In practice

Two services that always shipped together

A retailer's pricing and promotions services were separate deployables owned by one team. Over a year, 80% of releases changed both, always in the same order, and the two shared a package of eleven types that both imported.

Constraints

  • One team owns both services
  • Shared types package versioned and released separately
  • No independent scaling requirement — both run two instances
  • Deployment order documented in the runbook

Decision

Merge the two services back into one deployable with two internal modules, keep the module boundary and its import rule, and delete the shared types package.

Why

The connascence between them was strong and high-degree, and it spanned the most expensive locality available — a network and two release pipelines. The team had already weakened the strength as far as it could go with a shared, versioned type package, and it had changed nothing about the coordination cost. Improving locality was the move that had been skipped, and it removed the release ordering entirely.

What it cost

They gave up the ability to scale or deploy the two independently, which nobody was using, and they accepted that a future need to separate them would cost the same work again in reverse. They kept the internal module boundary and its enforced import rule specifically so that a future split would start from a clean edge rather than from a tangle.

Two components keep changing together

As a developer

Introduces a shared library holding the types both need, so at least the duplication is gone and the compiler catches mismatches. The coupling is now typed, which feels like progress.

As an architect

Reads it as evidence about the boundary itself. Components that always change together belong together; a shared library makes the coupling safer to hold without making it cheaper to carry, because the release coordination is unchanged. The question to answer first is whether the split was ever justified by an independent reason — different scaling, different teams, different availability — and if not, moving them closer beats making the distance more comfortable.

Your team maintains a `utils.ts` with forty exported functions, imported by most of the codebase. Nobody has complained about it. Should you act?Reveal

It is coincidentally cohesive — nothing in it changes for the same reason — but that alone is not a reason to spend a week on it. Look for the actual costs before acting: does it create import cycles, does it force unrelated code to be loaded together, do two functions in it now disagree about the same rule, and does it appear in an unusual number of merge conflicts? If none of those is true, the file is untidy and harmless, and rewriting it is a preference. If it is a merge-conflict hotspot or the compilation bottleneck, move each function next to the code that uses it, which is usually a mechanical change. Cohesion problems earn attention through their symptoms, not through their category.

Key takeaways

  • Cohesion means changing together, and change history is the evidence — not names or categories.
  • Grouping by technical kind (`validators`, `helpers`, `models`) is logical cohesion and is one of the weakest arrangements.
  • Connascence gives three dials: strength, locality and degree.
  • Prefer reducing degree and improving locality over merely weakening strength.
  • Two components that always change together are telling you the boundary is in the wrong place.