Refactor Smarter: Understanding Connascense in Software Design 🚀

Refactor Smarter: Understanding Connascense in Software Design 🚀

The concept of connascense, introduced by Meilir Page-Jones in the 1996 book "What Every Programmer Should Know About Object-Oriented Design," is essential for understanding software coupling clearly:

"Two components are connascent if a change in one would require the other to be modified to preserve overall correctness."

Connascense describes how tightly components are coupled and is classified into:

  • Static Connascense: Identifiable at compile time.
  • Dynamic Connascense: Identifiable only during runtime.

Reducing connascense significantly enhances maintainability and clarity.

Types of Connascense (Worst to Best):

  1. Identity 🔗 (Dynamic)
  2. Execution Order 📅 (Dynamic)
  3. Timing ⏱️ (Dynamic)
  4. Value 🔢 (Static)
  5. Type 📐 (Static)
  6. Meaning (Semantics) 🧠 (Static)
  7. Position 📍 (Static)
  8. Name 🔤 (Static)

Clear Java Examples:

1. Identity 🔗 (Dynamic)

When a system's behavior depends on using the exact same object instance across components, you have connascense of identity.

🔴 Before:

public class AccessManager {
    private Set<User> authorizedUsers;

    public boolean isAuthorized(User user) {
        return authUsrs.contains(u); // ❌ compare by instance
    }
}

User user1 = new User("alice");
User user2 = new User("alice");
accessManager.isAuthorized(user2); // ❌ false        

Why it's bad: Even if the user has the same ID, it may not be the exact same object. If equals() and hashCode() are not properly implemented, this breaks behavior unexpectedly.

🟢 Fix: Avoid relying on object identity; match by user ID instead.

public class AccessManager {
    private Set<String> authorizedUserIds;

    public boolean isAuthorized(User user) {
        return authIds.contains(u.getId()); // ✅ compare by value
    }
}        

Why it's better: The system no longer depends on shared object references. Instead, it compares logical identifiers, making the behavior correct and more robust. You eliminate fragile reliance on object identity and work with meaningful, logical equivalence.

2. Execution Order 📅 (Dynamic)

When one operation must happen before another and this dependency is not enforced by code structure, that's connascense of execution order.

🔴 Before:

cache.clear();
data.refresh();        

Why it's bad: If someone swaps the lines or calls them separately, the system may behave incorrectly.

🟢 Fix: Enforce order through encapsulation.

data.refreshAfterClearingCache();        

Why it's better: The correct order is built into the method, making misuse harder.

3. Timing ⏱️ (Dynamic)

When operations must happen with certain timing constraints (e.g. wait for resource readiness), it's connascense of timing.

🔴 Before:

Thread thread = new Thread(database::connect);
thread.start();
Thread.sleep(1000);
database.query();        

Why it's bad: It assumes a delay is enough — which is unreliable and fragile.

🟢 Fix: Use proper async composition.

CompletableFuture.runAsync(database::connect)
                 .thenRun(database::query);        

Why it's better: Guarantees that the query runs only after the connection is complete.

4. Value 🔢 (Static)

When two components rely on the same literal value, and a change in that value requires changing all components, it's connascense of value.

🔴 Before:

if(status.equals("ACTIVE")) {}        

Why it's bad: The value is a magic string — if you mistype it or need to change it, many places may break.

🟢 Fix: Use enums or constants.

enum Status { ACTIVE, INACTIVE }
if(status == Status.ACTIVE) {}        

Why it's better: Enums make valid values explicit, prevent typos, and centralize changes.

5. Type 📐 (Static)

When a method or component depends on a specific type, that’s connascense of type.

🔴 Before:

void saveData(ArrayList<String> data) {}        

Why it's bad: The method will not accept other list implementations, reducing flexibility.

🟢 Fix: Use abstract types.

void saveData(List<String> data) {}        

Why it's better: Accepts any List implementation — more reusable and testable.

6. Meaning (Semantics) 🧠 (Static)

When the meaning of values or parameters is implicit or unclear, you have connascense of meaning.

🔴 Before:

boolean validate(int x, int y) {}        

Why it's bad: What do x and y represent? Latitude? Width and height?

🟢 Fix: Clarify meaning with names.

boolean validateCoordinates(int latitude, int longitude) {}        

Why it's better: The code documents itself, reducing cognitive load and errors.

7. Position 📍 (Static)

When meaning is derived only from argument position, it's connascense of position.

🔴 Before:

user.update("John", "Doe", 30);        

Why it's bad: Easy to confuse the order — especially if types are the same.

🟢 Fix: Use a named parameter object.

user.update(new UserDetails("John", "Doe", 30));        

Why it's better: You reduce ambiguity and enforce structure.

8. Name 🔤 (Static)

When components rely on matching names (e.g. method names, field names), it's connascense of name.

🔴 Before:

class User {
    public String email;
}
user.email = "test@example.com";        

Why it's bad: Any change to the field name breaks every place where it’s used.

🟢 Fix: Use getters and setters.

class User {
    private String email;

    public void setEmail(String email) {
        this.email = email;
    }
}
user.setEmail("test@example.com");        

Why it's better: You encapsulate access, making future changes safer and centralized.

Connascense Overview 📊:

Here’s a quick reference to prioritize refactoring from the most harmful to the most acceptable forms of connascense:

Refactoring Priority Guide 📊:

Here’s a quick reference to prioritize refactoring from the most harmful to the most acceptable forms of connascense:

  • 🔗 Identity — components must share the same object instance. 🚨 Most fragile
  • 📅 Execution Order — correctness depends on calling sequence.
  • ⏱️ Timing — relies on assumptions about execution delays or readiness.
  • 🔢 Value — duplicated literals lead to fragile dependencies.
  • 📐 Type — tightly coupled to specific classes or implementations.
  • 🧠 Meaning (Semantics) — unclear parameter intent increases misunderstandings.
  • 📍 Position — argument meaning depends on order.
  • 🔤 Name — depends on consistent naming. ✅ Most flexible


Article content


Conclusion 🎯: 🎯: 🎯:

Effectively identifying and managing connascense dramatically improves your software's quality. Embrace clear coding practices to create robust, maintainable applications.

Great article, Antonio 👏 We often talk about clean code as an aesthetic goal, but this approach ties it directly to sustainable software design. Refactoring with an awareness of these dependencies allows us to write more maintainable, flexible code that aligns better with business evolution. Thanks for sharing such a clear and practical perspective 💡

To view or add a comment, sign in

More articles by Antonio Évora Gala

  • Is Your Code TRUE?

    What if there was a simple litmus test for great code? Not a checklist of rigid rules, but a mindset that makes your…

Others also viewed

Explore content categories