Connascence Guidelines: Rules for Sustainable Software DesignApplying Connascence Rules of Degree and Locality

The Brutal Truth About Your "Decoupled" Code

Most developers think they are writing clean, decoupled code because they use interfaces or follow a trendy folder structure they found on a tech blog. In reality, they are often just shuffling complexity from one place to another without understanding the underlying physics of software. Meilir Page-Jones introduced the concept of Connascence in his 1996 seminal work, What Every Programmer Should Know About Object-Oriented Design, and it remains the most brutally honest metric for measuring software rot. It doesn't care about your design pattern preferences; it cares about the ripple effect of a single change. If changing a method signature in one service requires you to manually update a string literal in a completely different part of the system, you aren't decoupled—you are fundamentally entangled in a way that will eventually lead to architectural paralysis.

At its core, connascence is a software quality metric that measures the level of coupling between components by analyzing how many elements must change simultaneously to maintain correctness. While "coupling" is often used as a vague buzzword in stand-up meetings, connascence provides a structured, scientific taxonomy to categorize exactly how "sticky" your code is. It asks a terrifyingly simple question: if I modify this piece of logic, what else breaks? If the answer involves searching through five different repositories or three disparate layers of your stack, your connascence is dangerously high. By identifying whether you are dealing with static or dynamic forms of this phenomenon, you can transform your architectural intuition into a measurable discipline, finally confronting the reality that every line of code is a liability until proven otherwise.

The Hierarchy of Strength: Why Your Code is Brittle

The first rule of connascence is Strength. Not all coupling is created equal, and Page-Jones ranked them in a hierarchy from weak (easy to refactor) to strong (hard to change). Static connascence, which includes things like Connascence of Name and Connascence of Type, is usually caught by your IDE or compiler before it ever hits production. However, as you move toward Connascence of Algorithm or Connascence of Value, things get messy. For instance, if two different functions must agree on a specific encryption algorithm to communicate, they are coupled by more than just an interface; they are coupled by a shared internal logic. This "Strength" is a direct indicator of how much mental overhead a developer needs to hold in their head just to make a "simple" update without breaking the entire universe.

The most dangerous forms are Dynamic Connascence, specifically Connascence of Execution and Connascence of Identity. This is the "brutally honest" part of the job: if your code only works because Function A happened to run before Function B, or because two different objects happen to share a specific memory address or global state, you have built a house of cards. This type of coupling is invisible to static analysis and often only reveals itself as a flickering "heisenbug" in your CI/CD pipeline. The goal isn't to reach zero connascence—that's impossible, as a system with no connection does nothing—but to move your code down the hierarchy. You should always prefer a weaker form of connascence over a stronger one; for example, refactoring a positional argument list into a named object takes you from Connascence of Position to Connascence of Name.

The brutal reality of modern development is that we often sacrifice strength for the sake of "cleverness" or "speed." We use magic strings, global constants, and hidden dependencies because they feel faster in the moment. But every time you opt for a stronger form of connascence, you are effectively taking out a high-interest loan on your future productivity. When Jim Weirich, a giant in the Ruby community, revitalized these concepts in the 2000s, he emphasized that high-strength connascence is a leading indicator of a system that is becoming "un-refactorable." If you can't describe the connection between two modules without using the words "it just has to be this way," you have reached a level of Strength that will eventually require a total rewrite once the original author leaves the company.

The Three Pillars: Strength, Degree, and Locality

While Strength tells you how "toxic" the connection is, Degree and Locality tell you how much it's going to hurt. Degree refers to the magnitude of the impact—how many elements are affected by this connection? A small amount of high-strength connascence (like a shared secret between two closely related functions) might be acceptable, but when that same secret is shared across fifty different classes, the Degree has reached a critical mass. This is why global state is the ultimate villain of software design; it has a Degree that encompasses your entire application. You can't change it without checking every single nook and cranny of your codebase, which is a recipe for developer burnout and regression bugs that no unit test can fully capture.

Locality is perhaps the most practical rule for the working developer. It dictates that the stronger the connascence, the closer the coupled elements should be. If two functions are coupled by Connascence of Algorithm, they should ideally live in the same file or, at the very least, the same module. When high-strength connascence spans across different microservices or organizational boundaries, you have created a distributed monolith. It is perfectly fine for a private helper function to be tightly coupled to its parent class; it is a disaster for your billing service to be tightly coupled to the internal database schema of your inventory service. The distance between the elements determines how likely a developer is to realize they need to change "the other thing" when they modify the first one.

Sustainable design is a constant balancing act between these three pillars. You want low Strength (weak coupling), low Degree (few affected parts), and high Locality (tightly grouped related changes). If you find yourself in a situation where Strength is high, you must work to lower the Degree or increase the Locality. This is why we encapsulate data: we are intentionally increasing locality to manage the inevitable connascence that comes with state management. Ignoring these rules doesn't make you an "agile" developer; it makes you a source of entropy. Every architectural decision you make should be run through the filter of: "Am I increasing the Strength, Degree, or distance of the connections in this system?"

Connascence in Action: Code Examples

To make this concrete, let's look at how we can move from Connascence of Position to Connascence of Name using Python and TypeScript. In the Python example below, notice how easy it is to pass the wrong value if you rely on position. This is a "stronger" form of connascence because the caller must know the exact order of arguments. By switching to keyword arguments or a typed configuration object in TypeScript, we weaken the connascence to "Name," which is much more resilient to change.

# POOR: Connascence of Position
# If the order changes, the logic silently breaks.
def create_user(first_name, last_name, email, age, is_admin):
    # logic here...
    pass

create_user("John", "Doe", "john@example.com", 30, False)

# BETTER: Connascence of Name (Python Keyword Arguments)
create_user(
    first_name="John", 
    last_name="Doe", 
    email="john@example.com", 
    age=30, 
    is_admin=False
)

In the TypeScript example, we can see how an interface further protects us. If we rename a property in the interface, the compiler will scream at us across the entire project. This moves us away from Connascence of Type or Value into Connascence of Name, which is the "Gold Standard" for manageable coupling. The compiler handles the "Strength" of the connection for us, ensuring that we don't accidentally ship a breaking change to a consumer that expects a specific data structure.

// BEST: Connascence of Name with Type Safety
interface UserConfig {
  firstName: string;
  lastName: string;
  email: string;
  age: number;
  isAdmin: boolean;
}

function createUser(config: UserConfig) {
  // Logic here...
}

// Even if we add new properties or change order, the 'Name' stays consistent.
createUser({
  firstName: "Jane",
  lastName: "Smith",
  email: "jane@example.com",
  age: 28,
  isAdmin: true
});

A common trap that developers fall into is the "DRY" (Don't Repeat Yourself) principle, which often inadvertently increases connascence. By trying to eliminate repetition, we create a shared dependency (Connascence of Algorithm) between two unrelated parts of the system. If those parts evolve in different directions, we are forced to add "if" statements and flags to our shared function, increasing complexity and making the code more brittle. Sometimes, a little bit of duplication is the price you pay for lower connascence and higher maintainability. Brutal honesty: it is better to have three slightly different functions than one "god function" that breaks the entire application every time you try to tweak it for a new use case.

The 80/20 Rule of Refactoring Connascence

You cannot fix every piece of bad coupling in a legacy codebase; you would never ship another feature. Instead, apply the Pareto Principle: 20% of your connascence is likely causing 80% of your production bugs and developer friction. Focus your refactoring efforts on "High Strength, High Degree, Low Locality" connections. These are the "Global Constants" that are used in 50 files, or the "Shared Database Tables" that four different teams are writing to simultaneously. These are the points of maximum leverage. If you can move a shared global state into a well-defined service (moving from Connascence of Value to Connascence of Name), you will see an immediate drop in the number of "random" regressions.

Prioritize fixing Dynamic Connascence first. While Static Connascence (like names and types) is annoying, Dynamic Connascence (like execution order and timing) is what causes system outages and data corruption. If your system relies on a specific sequence of asynchronous events that aren't explicitly orchestrated, you are playing a dangerous game of Russian Roulette with your uptime. Mapping out these dynamic dependencies and converting them into explicit, state-machine-driven logic is the highest-value work a senior engineer can perform. It isn't as flashy as building a new feature, but it is what ensures the system will still be standing two years from now.

Analogies for the Sleep-Deprived Developer

To help you remember these rules when you're staring at a PR at 4 PM on a Friday, think of software coupling like a shared toothbrush versus a shared bathroom. A shared bathroom (Weak Connascence/Locality) is a managed dependency; you might have to wait your turn, but your internal state remains separate. A shared toothbrush (Strong Connascence) is an intimate, high-risk connection where one person's "change" (bacteria) directly and immediately affects the other. Most modern "Microservices" are actually just people trying to share the same toothbrush across different houses via a long, fragile string. If you find your services are that tightly linked, you don't have a distributed system; you have a distributed disaster.

Another useful analogy is Synchronized Swimming vs. an Assembly Line. Synchronized swimming is Connascence of Timing; if one person is a millisecond off, the whole performance looks terrible. It requires extreme coordination and constant rehearsal. An assembly line, however, is Connascence of Name (the part fits the slot). As long as the part meets the specification, it doesn't matter if the person at Station A finished their task five minutes or five seconds before the person at Station B. Aim for the assembly line. It's less "beautiful" to watch, but it's a lot easier to scale and repair when someone gets sick or a machine breaks down.

Summary of Key Actions for Sustainable Design

If you want to start applying these rules tomorrow, follow these five steps to begin auditing your codebase:

  1. Audit Your Arguments: Look for functions with more than three positional arguments. Convert them to named objects or keyword arguments to move from Connascence of Position to Connascence of Name.
  2. Increase Locality: If two classes are constantly changing together but live in different folders, move them into the same module. If they are in different services, reconsider if they should be one service.
  3. Kill Global State: Identify one global variable or singleton and refactor it so that its data is passed explicitly. This lowers the Degree of connascence significantly.
  4. Formalize Timing: If you have code that "only works if the API returns in under 200ms," move that Connascence of Timing into an explicit queue or state machine that can handle delays gracefully.
  5. Prefer Interfaces over Implementations: Use TypeScript or Python protocols to ensure you are coupling to a Name and Type rather than a specific Algorithm or Identity.

Conclusion: The Long Game

Sustainable software design isn't about following a set of "shalt-nots" from a dusty textbook; it's about acknowledging the reality of change. Code is never static. It is a living, breathing entity that will either evolve or rot. Connascence gives you the vocabulary to talk about that rot before it smells too bad to ignore. By being brutally honest about the strength, degree, and locality of your connections, you can build systems that don't just work today, but are actually enjoyable to work on three years from now. It requires discipline and the willingness to push back against "quick fixes," but the payoff is a codebase that feels like an asset rather than an anchor.

Ultimately, the goal of every developer should be to leave the code better than they found it. Using the rules of connascence is how you prove you've done that. It moves the conversation from subjective opinions—"I don't like this pattern"—to objective analysis—"This increases the Strength and Degree of our coupling across service boundaries." That is the hallmark of a professional engineer. Stop worrying about being "clever" and start worrying about being "sustainable." Your future self, and your teammates, will thank you.