Featured image of post Fundamentals of Software Architecture — Chapter 3: Modularity

Fundamentals of Software Architecture — Chapter 3: Modularity

Exploring the art and science of organizing code into logical, maintainable, and scalable units through cohesion, coupling, and connascence analysis.

Note: This blog post is based on my personal notes but rewritten and restructured by AI.

Chapter 3: Modularity

Chapter 3 explores the concept of modularity — the art and science of organizing code into logical, maintainable, and scalable units. It dives into definitions, metrics, and guidelines for designing modular software, using both classic and modern analysis techniques.


What Is Modularity?

Modularity is the logical grouping of related code. While platforms offer packaging mechanisms (e.g., packages in Java, namespaces in .NET), modularity refers to how we conceptually organize code to improve clarity and maintainability.

Example: A Java package like com.mycompany.customer would logically contain all customer-related code.

Why It’s Important

  • It’s a foundational architectural trait — even if it’s not explicitly mentioned in requirements
  • Without it, systems become chaotic and hard to maintain

Key Takeaways

  • Modularity is logical, not always physical
  • It’s critical for reusability, clarity, and scalability

How to Use It

  • Use packages, folders, and namespaces to group related code logically
  • Design modules around domain functionality or bounded contexts

Measuring Modularity

To understand and improve modularity, we use three main concepts:

1. Cohesion

Cohesion = How closely related the internal parts of a module are.

  • High cohesion → Everything in the module works together
  • Low cohesion → Unrelated things are grouped together (bad)

Why It’s Important

High cohesion means modules are easier to understand, maintain, and reuse.

Key Takeaways

  • Aim for functional cohesion (the best form)
  • Avoid coincidental cohesion (the worst)

How to Use It

  • Use tools that calculate LCOM (Lack of Cohesion in Methods) to spot problem areas
  • Refactor modules/classes with unrelated responsibilities

2. Coupling

Coupling = The degree to which a module depends on others.

  • Afferent coupling (Ca) = Incoming connections (how many other modules use this one)
  • Efferent coupling (Ce) = Outgoing connections (how many modules this one uses)

Why It’s Important

  • High coupling increases fragility and change risk
  • Low coupling improves independence and testability

Key Takeaways

  • Lower is better for most coupling
  • Be cautious with highly coupled modules

How to Use It

  • Use tools to identify modules with high Ce or Ca
  • Refactor to reduce unnecessary dependencies

3. Abstractness, Instability & Distance from Main Sequence

These metrics help evaluate modular balance in object-oriented systems.

Abstractness (A)

Ratio of abstract elements (interfaces, abstract classes) to total.

Formula: A = #abstract / (#abstract + #concrete)

Instability (I)

Formula: I = Ce / (Ce + Ca)

Indicates volatility — how easily a module may break due to changes.

Distance from Main Sequence (D)

Measures deviation from the ideal balance.

Formula: D = |A + I - 1|

  • Near 0 = balanced
  • High = problematic (too abstract or too concrete)

Why It’s Important

  • Helps spot fragile or useless modules
  • Guides toward structural balance

Key Takeaways

  • Aim for low D (close to the “main sequence”)
  • Use visual diagrams to identify modules in the zone of pain or zone of uselessness

How to Use It

  • Use static analysis tools to compute A, I, D
  • Refactor modules to move closer to ideal balance

Connascence: A Deeper Look at Coupling

Connascence = If changing one component forces another to change to maintain correctness.

Static Connascence (Code-Level, Compile-Time)

TypeDescriptionExample
CoN (Name)Shared method/variable namesgetCustomer()
CoT (Type)Shared data typesint age vs String age
CoM (Meaning)Shared conventions or magic valuesint TRUE = 1
CoP (Position)Parameter order dependenciesupdate(String name, ID)
CoA (Algorithm)Shared logic/algorithmsShared hashing logic
CoI (Identity)Shared references or resourcesShared distributed queue

Dynamic Connascence (Runtime-Level)

TypeDescriptionExample
CoE (Execution)Order of operations matterssend() before setSubject()
CoT (Timing)Concurrent operations cause race conditionsMultithreading errors
CoV (Values)Multiple values must be changed togetherDB transactions
CoI (Identity)Shared runtime identityShared cache or singleton

Connascence Properties

PropertyMeaning
StrengthHow hard is it to refactor? Prefer weaker types.
LocalityIs the connascence local (same module) or remote?
DegreeHow many components are affected by the dependency?

Why It’s Important

  • Connascence is actionable — it tells you how coupling happens
  • Helps guide refactoring efforts with precision

Key Takeaways

  • Prefer static over dynamic connascence
  • Convert strong connascence to weaker forms
  • Use locality: Strong dependencies are okay when close, not when far apart

How to Use It

  • During refactoring, reduce CoM to CoN (e.g., use constants)
  • Apply Weirich’s rules:
    • Rule of Degree: Convert strong to weak
    • Rule of Locality: Use weaker forms as distance increases

Unifying Coupling and Connascence

This section shows how connascence refines traditional coupling.

  • Traditional coupling = “What is connected”
  • Connascence = “How it’s connected”

Why It’s Important

  • Offers a richer vocabulary and tools for evaluating dependencies
  • Bridges classic structured programming with modern design practices

How to Use It

  • Use coupling for high-level analysis
  • Use connascence for precise refactoring strategies

From Modules to Components

  • Module = Logical unit of related code
  • Component = Physical deployment unit
  • A component implements one or more modules
  • Components are the primary architectural building blocks

Why It’s Important

Understanding this distinction is crucial for deployment, scalability, and integration.

How to Use It

  • Package modules into components thoughtfully (e.g., JARs, DLLs, microservices)
  • Design components to be independently deployable and loosely coupled

Final Summary

Modularity is a critical architectural principle that ensures code remains manageable as systems grow. Chapter 3 outlines how to measure and improve modularity using cohesion, coupling, and connascence. These tools help architects identify structural issues and refactor toward healthier systems. Understanding the distinction between modules (logical) and components (physical) is key for deploying scalable software.

Most Useful Insights

Modularity = logical grouping, not necessarily physical separation
Cohesion: Higher = better
Coupling: Lower = better
✅ Use LCOM to measure internal cohesion
✅ Use Ca / Ce to analyze dependency structure
✅ Use Abstractness, Instability, D for balance metrics
Connascence helps understand and refactor coupling
✅ Prefer static, weak, local connascence
✅ Use components as the main deployable units
Trade-offs are inevitable — use metrics to choose wisely

Built with Hugo
Theme Stack designed by Jimmy