Connascence
Connascence unifies coupling and cohesion into one vocabulary. Static connascence is compile-time dependency; dynamic is runtime. Strength, locality, and degree tell you how costly the dependency is and which refactors reduce it most.
Two teams at the B2B platform were arguing about a refactoring. The billing team wanted to extract the payment retry logic into its own module. The order team objected: “you’ll break our retry coordination.” The argument stalled because neither team had a shared vocabulary for what kind of dependency existed or how bad it was. Was it that both modules used the same retry constant (name coupling)? That the order service expected billing to call back in a specific sequence (execution-order coupling)? That both modules had to agree on the meaning of a status integer (semantic coupling)? Each type of dependency has a different cost and a different refactoring path — but without a vocabulary for them, the conversation circled endlessly. Connascence is that vocabulary.
What connascence is
Meilir Page-Jones introduced connascence in 1992 as a unifying metric that subsumes both coupling and cohesion into one vocabulary. Two components are connascent if a change to one requires a corresponding change to the other. The framework classifies why that change is required — whether it is a shared name, a shared type, a shared algorithm, a shared execution order, or a shared timing — and grades how costly the dependency is along three dimensions: strength, locality, and degree.
The coupling ladder from lesson 01 told you what is shared (internals, shared state, control flags, data structures, primitives). Connascence tells you why a change to one component forces a change to another, and it extends the vocabulary to runtime dependencies that the coupling ladder does not reach.
Static connascence: compile-time dependencies
Static connascence is detectable without running the program. A type checker or linter can find it.
Connascence of Name (CoN): two components must agree on a name. The billing engine calls order.getConfirmedItems(). If the order module renames getConfirmedItems to getApprovedLineItems, billing breaks. CoN is the weakest static connascence — a rename refactor fixes it everywhere automatically.
Connascence of Type (CoT): two components must agree on a type. The billing engine accepts (order: ConfirmedOrder). If the order module changes ConfirmedOrder to ApprovedOrder, billing’s parameter type becomes stale. CoT is stronger than CoN — a type rename requires updating every reference that declares the type, not just callers.
Connascence of Meaning (CoM) (also called Connascence of Convention): two components must agree on what a value means. If the order status is encoded as an integer (0 = pending, 1 = confirmed, 2 = cancelled) and billing checks if (order.status === 1), both modules are coupled to the integer convention. When the order module adds a new status (3 = partially fulfilled), billing’s integer check silently misclassifies the new state. CoM is significantly stronger: the coupling is invisible to the compiler and often produces silent runtime failures rather than build failures.
Connascence of Position (CoP): two components must agree on the order of arguments. If billing calls a shared utility as applyDiscount(orderId, percentage, reason) and the utility’s signature is later changed to applyDiscount(reason, percentage, orderId), the caller silently passes the wrong values to each parameter — the code compiles, the types may even match, but the behaviour is wrong. CoP is stronger than CoM because positional coupling is invisible to the caller’s source code: the function name has not changed, the types may not have changed, yet the contract has shifted under the caller. The weakening move is to replace positional arguments with a named parameter object — applyDiscount({ orderId, percentage, reason }) — so the call site is independent of argument order.
Connascence of Algorithm (CoA): two components must use the same algorithm. If billing hashes order IDs for audit log keys using the same MD5 implementation that the order module uses to generate them, both must stay in sync on the hashing algorithm. Changing the algorithm in one without the other produces keys that do not match. CoA is strong: the dependency is not expressed in any type or interface — it lives in the implementation.
Dynamic connascence: runtime dependencies
Dynamic connascence is not detectable by analysis — it requires observing the running system.
Connascence of Execution (CoE): two components must execute in a specific order for correct behavior. The billing module must call validateOrder() before generateInvoice() before triggerPayment(). If any caller skips a step or reverses the order, the system fails at runtime in ways that may not be immediately obvious. CoE is stronger than all static forms because it is invisible to static analysis and the failure mode depends on timing.
Connascence of Timing (CoT*): two components must execute within a time window of each other. A billing webhook receiver must process a payment confirmation within thirty seconds of the order service posting it, or the order times out. This is runtime scheduling coupling: the dependency is on the clock, not on code structure.
Connascence of Values (CoV): two components must agree on the value of related data at runtime. If the billing engine and the order service both maintain a copy of the order total, they must agree. When billing applies a late discount and updates its copy but the order service does not update its copy, the system has inconsistent state. This is distributed state coupling — the strongest and most dangerous form.
Connascence of Identity (CoI) (strongest): two components must reference the same object in memory or the same entity in storage. If billing holds a direct reference to the same Order object that the order service is mutating, both are coupled to the object’s identity and mutation history. This is the runtime equivalent of content coupling.
Three grading dimensions: strength, locality, degree
Knowing the type of connascence is not enough — you also need to know how costly it is. Three dimensions grade the cost:
Strength is the primary axis and follows the type ordering described above. CoN is weaker than CoM; CoE is weaker than CoV. Stronger connascence is more costly to manage because it requires more coordination when either side changes, and failures are often less visible.
Locality measures how close the connascent components are. Two classes in the same package sharing CoM (a convention about integer status codes) is local connascence — it can be enforced by code review and is easy to audit. Two services in different bounded contexts sharing CoM through a shared event schema is distant connascence — each side may evolve independently without noticing the other has changed the meaning of the value. Distant connascence is far more dangerous than local connascence of the same strength.
Degree measures how many elements are involved. CoN between two functions is a point dependency — one rename. CoN between a widely-used constant and thirty call sites is high-degree — one rename plus thirty updates. High-degree connascence amplifies the cost of any change proportionally.
▸Why this works
Why does locality matter so much? Because the rule of thumb from Page-Jones is: strong connascence should be local. If you have a connascence of algorithm — two components that must agree on the same hashing implementation — that dependency should be inside one module, not across a service boundary. If it must cross a boundary, weaken it: encode the algorithm’s output (the hash) as a type (CoT) rather than requiring both sides to re-implement the hash (CoA). Locality is the lever you pull when you cannot eliminate connascence: bring it inside, where a single change fixes it.
Using connascence to rank refactors
The power of connascence is that it turns “this code is messy” into “here is the specific dependency to weaken and here is how.” Three rules guide the ranking:
1. Weaken first, then eliminate. A connascence of meaning (integer status codes) can be weakened to connascence of type (a typed enum) before being eliminated entirely (event-driven communication with no shared status). Weakening is cheaper than eliminating and is often enough.
2. Localize second. If a strong connascence must exist, pull it inside a single module’s boundary. Two services sharing a CoM can be refactored so that one service owns the convention and translates it for the other — now only one side holds the strong connascence, and the other side sees only CoN (a method name) or CoT (a type).
3. Reduce degree. If you cannot weaken or localize, reduce the number of elements involved. Replace the integer status with a typed constant that is imported by reference — now only one definition exists and all thirty call sites hold CoN rather than CoM.
In the B2B platform, when billing and ordering shared the integer status convention (CoM, distant, degree 30), the ranked refactor was: introduce a OrderStatus enum that both sides import (weakened to CoT, localized to one shared library, degree reduced to one definition). The thirty integer checks become thirty typed comparisons — the compiler now catches the coupling instead of silent runtime misclassifications.
▸lesson.inset.note
Connascence does not replace the coupling ladder or cohesion analysis — it extends them. The coupling ladder is fast for classifying the existing shape of a module boundary. Connascence is precise for deciding which refactor to do next. When two modules have a specific dependency that is causing pain, the connascence vocabulary tells you exactly what kind of dependency it is and what transformation reduces it.
The billing engine and the order service both parse a JSON payload and both must agree that the field `order_status` can be one of four string values: 'pending', 'confirmed', 'cancelled', 'disputed'. The billing team adds a fifth value 'partially_fulfilled' and the order service starts emitting it. Billing silently mishandles the new status for two weeks before anyone notices. What connascence type caused this, and why was it invisible?
The billing engine must call validateOrder(), then generateInvoice(), then triggerPayment() in exactly that sequence, or the payment gateway rejects the request. A new developer reverses the last two steps. The bug is caught only in production when the first payment attempt fails. What connascence type is this?
The billing engine and the reporting service both share a Connascence of Meaning on how payment status integers map to states (0=pending, 1=paid, 2=failed, 3=refunded). This convention is used across 40 call sites in both services. You cannot eliminate the connascence entirely this sprint. Using the connascence grading rules, what is the ranked refactoring sequence?
- 01What is the difference between static and dynamic connascence? Give one example of each.
- 02Name the three dimensions for grading connascence and explain what each measures.
- 03What is the ranked refactoring sequence when you cannot eliminate a connascence in one step?
Connascence is the vocabulary that unifies coupling and cohesion. Two components are connascent when a change to one requires a corresponding change to the other. The classification names why: Connascence of Name (shared identifier), Type (shared type declaration), Meaning (shared value convention), Position (shared argument order), Algorithm (shared implementation), Execution (shared runtime sequence), Timing (shared time window), Values (shared runtime data), Identity (shared object reference). The first five are static — detectable at compile time. The last four are dynamic — detectable only at runtime, and therefore more dangerous.
Grading a connascence requires three dimensions: strength (what type it is, from CoN to CoI), locality (how far apart the connected components are — same module to across service boundaries), and degree (how many elements are involved). The change cost is proportional to all three.
The refactoring priority rule: weaken the connascence type first (CoM to CoT is a safe, mechanical step), then localize it (pull the strong dependency inside one module’s boundary), then reduce its degree (consolidate to one canonical definition). These steps are cheaper than full elimination and often sufficient to make the dependency manageable. Applied to the coupling ladder and cohesion analysis from lessons 01 and 02, connascence completes the toolkit: the coupling ladder tells you what is shared, cohesion tells you what should be grouped, and connascence tells you which specific dependency to weaken next.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.