Cohesion, Coupling, and SOLID — Software Design Principles That Survived 50 Years
In December 1972, David Parnas published a short paper titled “On the Criteria To Be Used in Decomposing Systems into Modules.”1 This paper argued that modules should be divided not by functional flow, but according to the principle of Information Hiding. Two years later in 1974, Stevens, Myers, and Constantine defined the terms Cohesion and Coupling, laying the foundation for structured design.2
50 years later, these concepts still play a central role in defining bounded contexts in microservice architectures, designing pure functions in functional programming, and implementing dependency rules in clean architecture. Why have these principles survived for so long?
The Beginning of Everything: Information Hiding
David Parnas’s 1972 paper was a turning point in software design philosophy. Instead of the mainstream functional decomposition approach of the time, he proposed a method of “hiding design decisions that are likely to change from other modules.”
Parnas’s core idea was simple. When decomposing a system, follow this sequence:
- Create a list of design decisions that are likely to change
- Have each decision handled by a single module
- Completely hide that decision from other modules
This was the concrete realization of Separation of Concerns. This principle, mentioned by Edsger Dijkstra in 1974’s “On the role of scientific thought,” aimed to “enable focusing on only one thing at a time.”3
Cohesion: Internal Unity of a Module
Cohesion, as defined by Stevens, Myers, and Constantine in 1974, measures “how closely related the components within a module are.”2 They classified cohesion into 7 levels, which remain valid today.
| Cohesion Type | Characteristics | Example | Quality |
|---|---|---|---|
| Coincidental | Arbitrarily grouped without logical relationship | Utility class | Worst |
| Logical | Groups similar functions | InputManager (keyboard+mouse+touch) | Poor |
| Temporal | Groups functions executed at the same time | Initialization module | Fair |
| Procedural | Grouped by sequential execution flow | Data processing pipeline | Fair |
| Communicational | Functions that manipulate the same data | Customer CRUD class | Good |
| Sequential | One function’s output is another’s input | Data transformation chain | Good |
| Functional | Performs one clear task | calculateTax(), sendEmail() | Best |
Functional cohesion is optimal because it perfectly aligns with the Single Responsibility Principle. Since there’s only one reason to change, maintainability is maximized.
# Coincidental cohesion (bad example)
class Utils:
def format_date(self, date): pass
def calculate_tax(self, amount): pass
def send_email(self, message): pass
def validate_password(self, password): pass
# Functional cohesion (good example)
class TaxCalculator:
def calculate(self, amount, tax_rate):
return amount * (1 + tax_rate)
class EmailService:
def send(self, recipient, subject, body):
# Email sending logic
pass
Coupling: Dependencies Between Modules
Coupling measures “how much modules depend on each other.”4 Yourdon and Constantine defined 6 types.
| Coupling Type | Characteristics | Example | Quality |
|---|---|---|---|
| Content | One module directly manipulates another’s internals | Direct global variable modification | Worst |
| Common | Share global data | Global state, singleton abuse | Poor |
| Control | Pass control information | Flag parameters | Poor |
| Stamp | Pass entire data structure but use only part | Pass whole object, use one field | Fair |
| Data | Pass only necessary data as parameters | Primitive type parameters | Good |
| Message | Message passing without parameters | Event-based communication | Best |
In practice, coupling is also measured with quantitative metrics:
- CBO(Coupling Between Objects): Number of other classes a class references
- Ca(Afferent Coupling): Number of modules that depend on this module
- Ce(Efferent Coupling): Number of modules this module depends on
- Instability: Ce/(Ca+Ce), 0(stable)–1(unstable)
# Stamp coupling (bad example)
def process_user(user_obj):
# Only uses user_obj.name but receives entire object
return f"Processing {user_obj.name}"
# Data coupling (good example)
def process_user(user_name: str):
return f"Processing {user_name}"
Connascence: Modern Reinterpretation of Coupling
In 1996, Meilir Page-Jones introduced the concept of Connascence in “What Every Programmer Should Know About Object-Oriented Design.”5 This expanded the existing coupling concept for the object-oriented era.
Connascence is evaluated on 3 axes:
- Strength: Degree of change difficulty
- Locality: Distance in code
- Degree: Number of affected elements
Page-Jones categorized Connascence into static and dynamic:
Static Connascence (analyzable from source code)
- Name: Same name reference
- Type: Same type usage
- Meaning: Shared meaning of specific values
- Position: Parameter order dependency
- Algorithm: Same algorithm implementation
Dynamic Connascence (only verifiable at runtime)
- Execution: Execution order dependency
- Timing: Time dependencies
- Value: Need for related values to match
- Identity: Same object reference requirement
Core principle: The greater the distance, the weaker the strength should be, and the stronger the strength, the higher the locality should be.
# Connascence of Position (bad example)
create_user("John", "Doe", 25, "john@email.com")
# Connascence of Name (good example)
create_user(
first_name="John",
last_name="Doe",
age=25,
email="john@email.com"
)
Law of Demeter: Practical Rules for Reducing Coupling
The Law of Demeter proposed by Ian Holland at Northeastern University in 1987 is also known as the “Principle of Least Knowledge.”6
The core is “only talk to your friends.” That is, an object should only interact with:
- Its own methods
- Methods of objects received as parameters
- Methods of objects it creates
- Methods of its direct component objects
# Law of Demeter violation (bad example)
class Order:
def get_total(self):
return self.customer.wallet.money.amount
# Law of Demeter compliance (good example)
class Order:
def get_total(self):
return self.customer.get_payment_amount()
Following this rule reduces chaining calls and minimizes the impact of intermediate object changes on clients.
Cohesion×Coupling Matrix
System quality based on combinations of cohesion and coupling:
| Cohesion↓Coupling→ | Low Coupling | Medium Coupling | High Coupling |
|---|---|---|---|
| High Cohesion | 🟢 Ideal | 🟡 Needs improvement | 🔴 Needs refactoring |
| Medium Cohesion | 🟡 Usable | 🟡 Average | 🔴 Problematic |
| Low Cohesion | 🔴 Needs decomposition | 🔴 Serious | 🔴 Needs redesign |
The best combination is high cohesion + low coupling, which is the ultimate goal of all design patterns and architectural principles.
Relationship Between SOLID Principles and Cohesion·Coupling
The SOLID principles presented by Robert C. Martin in 2000’s “Design Principles and Design Patterns” are the modern evolution of cohesion and coupling concepts.7
| SOLID Principle | Definition | Cohesion·Coupling Contribution |
|---|---|---|
| SRP | A class should have only one reason to change | Pursues functional cohesion |
| OCP | Open for extension, closed for modification | Reduces coupling through abstraction |
| LSP | Subtypes should be substitutable for their base types | Maintains interface coupling |
| ISP | Clients shouldn’t depend on interfaces they don’t use | Increases interface cohesion |
| DIP | Depend on abstractions, not concretions | Inverts dependency coupling |
Each principle provides specific methods for increasing cohesion or reducing coupling.
# SRP: Single Responsibility Principle (functional cohesion)
class TaxCalculator:
def calculate_tax(self, amount: float, rate: float) -> float:
return amount * rate
class TaxPersister:
def save_tax_record(self, record: TaxRecord) -> None:
# DB saving logic
pass
# DIP: Dependency Inversion Principle (low coupling)
class PaymentProcessor:
def __init__(self, payment_gateway: PaymentGateway):
self._gateway = payment_gateway # Depend on abstraction
def process(self, amount: float) -> bool:
return self._gateway.charge(amount)
Empirical Research: Effects by the Numbers
Whether cohesion and coupling actually affect software quality has been verified in multiple empirical studies.
Basili, Briand & Melo (1996)
This is the most important early validation study.8 Basili et al. collected CK metrics from 8 NASA C++ projects and analyzed their relationship with defect occurrence. The results were clear — CBO, RFC, WMC, DIT, NOC metrics significantly predicted class fault-proneness. Particularly, classes with higher CBO (coupling) had distinctly higher probability of defects.
Subramanyam & Krishnan (2003)
This study analyzed the relationship between CK metrics and defects in commercial software written in C++ and Java.9 The key findings were twofold. First, there was a significant positive relationship between increased CBO values and increased defect count. Second, this effect varied by programming language — in C++, defects increased more steeply with CBO value increases.
Gyimóthy, Ferenc & Beszédes (2005)
A large-scale study of the open-source Mozilla project using logistic regression analysis confirmed CBO, LOC, and RFC as the most effective metrics for defect prediction.10 Meanwhile, LCOM and DIT had relatively lower predictive power.
Practical Thresholds
| Metric | Recommended Threshold | Basis |
|---|---|---|
| CBO | 9 or less (per single member) | Sahar et al.(2010)11, Official Microsoft Visual Studio documentation |
| WMC | 20–50 or less | Chidamber & Kemerer(1994)12 recommendation |
| DIT | 5 or less | Official Visual Studio .NET documentation |
However, these thresholds are not absolute standards. Appropriate values vary by project domain, language, and framework. What’s important is observing trends — if a particular class’s CBO is significantly higher than the project average, it should be considered for refactoring.
Modern Applications: MSA and Functional Programming
Microservice Architecture
Microservices maintain high cohesion at the Bounded Context level while pursuing low coupling between services through APIs or events.
- Domain cohesion: Concentrate same business domain logic in one service
- Data coupling: Prohibit database sharing between services
- Interface coupling: Communication through REST API, GraphQL, event streams
# High cohesion: All order-related functions in one service
order-service:
- create_order()
- cancel_order()
- calculate_total()
- apply_discount()
# Low coupling: Communication only through events
events:
- OrderCreated → inventory-service
- OrderCancelled → payment-service
- PaymentCompleted → shipping-service
Functional Programming
The functional paradigm achieves perfect functional cohesion and minimal coupling through pure functions and immutability.
-- Perfect functional cohesion: Only performs tax calculation
calculateTax :: Float -> Float -> Float
calculateTax amount rate = amount * rate
-- Minimal coupling: No external state dependencies
processOrder :: Order -> Float -> Order
processOrder order taxRate =
order { orderTotal = baseAmount + calculateTax baseAmount taxRate }
where baseAmount = sum (map itemPrice (orderItems order))
50 Years of Lessons
The reason cohesion and coupling concepts have survived half a century is that they reflect human cognitive limitations.
As shown in Miller’s “magical number 7±2” (1956)13, humans have limits on the amount of information they can process simultaneously. High cohesion reduces cognitive burden by keeping related things together, while low coupling enables independent thinking.
Technology has continued to change — from procedural to object-oriented programming, from monoliths to microservices, from imperative to functional — but how humans handle complexity hasn’t changed. That’s why these principles will continue to be valid.
The clean architecture, domain-driven design, and reactive systems we pursue today are all based on the same philosophy of “related things close, unrelated things far.” This simple idea presented by Parnas and Constantine 50 years ago will continue to be with us as long as software exists.
Footnotes
-
Parnas, D. L. (1972). “On the Criteria To Be Used in Decomposing Systems into Modules.” Communications of the ACM, 15(12), 1053–1058. ↩
-
Stevens, W., Myers, G., & Constantine, L. (1974). “Structured Design.” IBM Systems Journal, 13(2), 115–139. ↩ ↩2
-
Dijkstra, E. W. (1974). “On the role of scientific thought.” EWD447. ↩
-
Yourdon, E. & Constantine, L. (1979). Structured Design: Fundamentals of a Discipline of Computer Program and Systems Design. Prentice-Hall. ↩
-
Page-Jones, M. (1996). What Every Programmer Should Know About Object-Oriented Design. Dorset House. ↩
-
Holland, I. (1987). “The Law of Demeter.” Northeastern University, Demeter Project. ↩
-
Martin, R. C. (2000). “Design Principles and Design Patterns.” objectmentor.com. ↩
-
Basili, V., Briand, L., & Melo, W. (1996). “A Validation of Object-Oriented Design Metrics as Quality Indicators.” IEEE Transactions on Software Engineering, 22(10), 751–761. ↩
-
Subramanyam, R. & Krishnan, M. S. (2003). “Empirical Analysis of CK Metrics for Object-Oriented Design Complexity.” IEEE Transactions on Software Engineering, 29(4), 297–310. ↩
-
Gyimóthy, T., Ferenc, R., & Siket, I. (2005). “Empirical Validation of Object-Oriented Metrics on Open Source Software for Fault Prediction.” IEEE Transactions on Software Engineering, 31(10), 897–910. ↩
-
Sahar, H. et al. (2010). Threshold research. Referenced from Microsoft Visual Studio Official Documentation. ↩
-
Chidamber, S. R. & Kemerer, C. F. (1994). “A Metrics Suite for Object Oriented Design.” IEEE Transactions on Software Engineering, 20(6), 476–493. ↩
-
Miller, G. A. (1956). “The Magical Number Seven, Plus or Minus Two.” Psychological Review, 63(2), 81–97. ↩