Cohesion, Coupling, and SOLID — Software Design Principles That Survived 50 Years

· # AI 개념
software design SOLID cohesion coupling clean architecture refactoring

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:

  1. Create a list of design decisions that are likely to change
  2. Have each decision handled by a single module
  3. 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 TypeCharacteristicsExampleQuality
CoincidentalArbitrarily grouped without logical relationshipUtility classWorst
LogicalGroups similar functionsInputManager (keyboard+mouse+touch)Poor
TemporalGroups functions executed at the same timeInitialization moduleFair
ProceduralGrouped by sequential execution flowData processing pipelineFair
CommunicationalFunctions that manipulate the same dataCustomer CRUD classGood
SequentialOne function’s output is another’s inputData transformation chainGood
FunctionalPerforms one clear taskcalculateTax(), 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 TypeCharacteristicsExampleQuality
ContentOne module directly manipulates another’s internalsDirect global variable modificationWorst
CommonShare global dataGlobal state, singleton abusePoor
ControlPass control informationFlag parametersPoor
StampPass entire data structure but use only partPass whole object, use one fieldFair
DataPass only necessary data as parametersPrimitive type parametersGood
MessageMessage passing without parametersEvent-based communicationBest

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:

  1. Strength: Degree of change difficulty
  2. Locality: Distance in code
  3. 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:

  1. Its own methods
  2. Methods of objects received as parameters
  3. Methods of objects it creates
  4. 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 CouplingMedium CouplingHigh 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 PrincipleDefinitionCohesion·Coupling Contribution
SRPA class should have only one reason to changePursues functional cohesion
OCPOpen for extension, closed for modificationReduces coupling through abstraction
LSPSubtypes should be substitutable for their base typesMaintains interface coupling
ISPClients shouldn’t depend on interfaces they don’t useIncreases interface cohesion
DIPDepend on abstractions, not concretionsInverts 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

MetricRecommended ThresholdBasis
CBO9 or less (per single member)Sahar et al.(2010)11, Official Microsoft Visual Studio documentation
WMC20–50 or lessChidamber & Kemerer(1994)12 recommendation
DIT5 or lessOfficial 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

  1. Parnas, D. L. (1972). “On the Criteria To Be Used in Decomposing Systems into Modules.” Communications of the ACM, 15(12), 1053–1058.

  2. Stevens, W., Myers, G., & Constantine, L. (1974). “Structured Design.” IBM Systems Journal, 13(2), 115–139. 2

  3. Dijkstra, E. W. (1974). “On the role of scientific thought.” EWD447.

  4. Yourdon, E. & Constantine, L. (1979). Structured Design: Fundamentals of a Discipline of Computer Program and Systems Design. Prentice-Hall.

  5. Page-Jones, M. (1996). What Every Programmer Should Know About Object-Oriented Design. Dorset House.

  6. Holland, I. (1987). “The Law of Demeter.” Northeastern University, Demeter Project.

  7. Martin, R. C. (2000). “Design Principles and Design Patterns.” objectmentor.com.

  8. 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.

  9. 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.

  10. 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.

  11. Sahar, H. et al. (2010). Threshold research. Referenced from Microsoft Visual Studio Official Documentation.

  12. Chidamber, S. R. & Kemerer, C. F. (1994). “A Metrics Suite for Object Oriented Design.” IEEE Transactions on Software Engineering, 20(6), 476–493.

  13. Miller, G. A. (1956). “The Magical Number Seven, Plus or Minus Two.” Psychological Review, 63(2), 81–97.

← The History of AI — From Perceptrons to Autonomous Agents LLM Compression Techniques Deep Dive — Quantization, Pruning, and Distillation →