Software engineering has accumulated decades of design wisdom: Separation of Concerns, Information Hiding, SOLID, Package Principles, DRY, KISS, Law of Demeter, and principles from functional programming. Each offers valuable guidance, but they've remained fragmented—independent heuristics learned separately, applied by intuition. I have long been struck by the similarities between them. For years, my conclusion was that most aimed at improving decoupling between software elements. This intuition was right, but incomplete.
The Independent Variation Principle (IVP) offers a unifying lens. It explains why these principles work, how they relate to each other, and when to apply them. This article examines IVP's relationship to major design principles and what it clarifies about each.
Table of Contents
- What Is IVP?
- The Fundamental Premise: Software Always Changes
- Domain Knowledge Over Heuristics: The IVP Difference
- The Core Insight: Architecture Is Knowledge Work
- Cohesion's Primacy: Why Coupling Is a Function of Cohesion
- Classic Design Principles (1960s–1990s)
- XP and Agile Principles (1990s–2000s)
- Systems-of-Systems Principles (1990s–2000s)
- Cloud-Native Principles (2010s)
- GoF Principles
- SOLID Principles
- Package Architecture Principles
- GRASP Principles
- Functional Programming Principles
- Summary: What IVP Brings
- Why Existing Principles Still Matter
- Practical Takeaways
- References
What Is IVP?
IVP can be stated in three equivalent formulations, each emphasizing a different aspect:
Pattern Formulation: Separate elements that vary independently; unify elements that vary dependently.
Causal Formulation: Separate what varies for different causes; unite what varies for the same cause.
Structural Formulation: Separate elements governed by different change driver assignments into distinct units; unify elements governed by the same change driver assignment within a single unit.
These three formulations are fully equivalent. The pattern formulation describes the observable behavior (independent vs. dependent variation). The causal formulation explains why things vary independently (different causes). The structural formulation provides the prescription (organize into units based on sets of change drivers).
Elements and Units
Elements and units are simply code constructs:
- Elements are atomic constructs: statements, expressions, fields, methods, functions
- Units are grouping constructs: classes, modules, packages, services, layers
Critically, what counts as an "element" or "unit" depends on the observation level, which is defined by the change drivers under consideration:
- At the method level, statements are elements and methods are units
- At the class level, methods are elements and classes are units
- At the package level, classes are elements and packages are units
- At the service level, packages are elements and services are units
IVP applies fractally at every level. The principle remains the same; only the granularity changes.
This generality is intentional. IVP is formulated so that "element" and "unit" are completely general constructs—they carry no assumption about programming paradigm, language features, or system type. Whether you're organizing statements into functions, functions into modules, modules into packages, packages into services, or services into systems, IVP applies. The abstraction level is determined by the change drivers you're analyzing, not by the principle itself.
The Two Directives
IVP yields two complementary directives:
- IVP-1 (Isolate Divergent Concerns): Elements governed by different change drivers should be in different units
- IVP-2 (Unify by Single Purpose): Elements governed by the same change driver should be in the same unit
The key insight: IVP operates at the level of causation rather than structure. It asks: What causes this code to change? That cause is called a change driver (denoted γ in formal notation).
The Fundamental Premise: Software Always Changes
IVP rests on a foundational premise that every experienced developer knows: software systems always need to change.
Requirements evolve. Business rules shift. Regulations update. Technologies become obsolete. User expectations grow. What seemed permanent yesterday becomes legacy today.
Research suggests that software maintenance and evolution account for up to 90% of total lifecycle costs. The ripple effects of change—where modifying one part of a system forces changes elsewhere—are a significant driver of this cost. Architecture exists, in large part, to manage change.
The question isn't whether your code will change, but how it will change and whether your architecture will accommodate that change gracefully.
IVP takes this reality seriously. It asks not "what does the code do?" but "what will cause the code to change?"
Domain Knowledge Over Heuristics: The IVP Difference
Traditional design principles are experience-based heuristics. They emerged from decades of practitioners noticing patterns: "when we do X, things tend to work out better." This is valuable wisdom, but it has limitations:
- Vague criteria: What exactly is a "responsibility"? When is something "likely to change"? Different architects answer differently.
- No guidance for novel situations: Heuristics work for familiar problems; they provide little help for genuinely new challenges.
- Apparent conflicts: Should I follow DRY or keep these modules separate? Should I add an interface or is that over-engineering? Heuristics don't resolve these tensions.
IVP takes a different approach: it asks you to ground your decisions in actual business domain knowledge, not accumulated experience and intuition alone.
From "Good Ideas" to Domain Analysis
Different principles approach design decisions in different ways:
SRP (heuristic): "Does this class have one responsibility?"
But what counts as "one"? Who decides?
OCP (heuristic): "Is this open for extension?"
But should it be? At what cost?
IVP (domain-grounded): "Who or what causes this code to change? Are there multiple independent sources of change affecting this code?"
IVP shifts the conversation from abstract principles to concrete domain analysis:
- What business stakeholders own this functionality? Different stakeholders = different change drivers.
- What external systems does this code depend on? Different systems = different change drivers.
- What regulations govern this behavior? Different regulatory bodies = different change drivers.
- What technical concerns are involved? Different technical domains = different change drivers.
Change Drivers Are Domain Facts
A change driver isn't an abstract concept—it's a concrete fact about your domain:
- The pricing team controls pricing rules →
γ_pricing - The compliance department owns regulatory logic →
γ_compliance - The infrastructure team manages deployment concerns →
γ_infrastructure - An external payment API has its own evolution schedule →
γ_payment_api
These aren't heuristics—they're facts about your business domain. You can identify them, name them, and verify them by looking at who requests changes and why.
The Epistemological Foundation
IVP's Knowledge Theorem formalizes this: maximal cohesion is equivalent to complete and pure embodiment of domain knowledge. A module has maximal cohesion when it:
- Contains all the knowledge relevant to its change driver (complete)
- Contains no knowledge relevant to other change drivers (pure)
- Exhibits all expected functional behavior and non-functional qualities (semantic)
The first two dimensions are structurally verifiable—you can analyze code to check them. The third requires domain expertise: does this module actually do what the business needs? This reflects that architecture is fundamentally knowledge work, not just structural arrangement.
This isn't a heuristic saying "cohesive modules are good." The Knowledge Theorem establishes a formal equivalence: cohesion reflects how well we have organized domain knowledge. High cohesion means your code structure matches your domain structure. Low cohesion means your code misrepresents what knowledge belongs together.
Practical Implications
This domain-grounding has practical consequences:
1. Decisions become more grounded. Instead of debating whether something "feels like" one responsibility, you can identify the change drivers involved. Either the code is affected by one change driver or multiple. This shifts the conversation from subjective intuition toward concrete domain analysis—though judgment remains necessary.
2. Reasoning transfers across contexts. Heuristics often fail in unfamiliar territory ("SOLID doesn't apply to my database schema"). IVP's change driver analysis works wherever things change—code, configuration, data, infrastructure, even team structure.
3. Domain experts become relevant. With heuristic-based principles, architects decide what's "right" based on experience. With IVP, domain knowledge matters. Understanding the business—who owns what, what changes independently—informs architecture directly.
4. Historical analysis becomes possible. You can mine version control history to discover actual change drivers empirically. When did things change together? When did they change independently? The answers guide future structure.
The Core Insight: Architecture Is Knowledge Work
Before diving into specific principles, I want to share what I consider IVP's most important claim: software architecture is fundamentally epistemological knowledge work.
Why "Epistemological"?
Epistemology is the branch of philosophy concerned with knowledge: What is knowledge? How do we acquire it? How do we organize and represent it?
Software, at its core, is embodied knowledge. Every line of code represents something someone knows about the domain:
- A pricing formula embodies knowledge about how the business calculates prices
- A validation rule embodies knowledge about what constitutes valid data
- An API contract embodies knowledge about how systems communicate
- A database schema embodies knowledge about entity relationships
When you write code, you're not just "programming"—you're formalizing knowledge into executable form. The business analyst knows the pricing rules; the code makes that knowledge precise, explicit, and operational.
This is why architecture matters: architecture determines how knowledge is organized. Good architecture organizes knowledge so that:
- Related knowledge lives together (cohesion)
- Unrelated knowledge stays separate (low coupling)
- Each piece of knowledge has one authoritative location (no contradictions)
- Knowledge can evolve independently when it changes independently
Bad architecture scatters related knowledge across modules, tangles unrelated knowledge together, duplicates knowledge in contradictory ways, and forces unrelated things to change together.
The Knowledge Theorem
The Knowledge Theorem proves that maximal cohesion (the goal all these principles pursue) is equivalent to:
- Complete embodiment of domain knowledge (nothing relevant missing)
- Pure embodiment of domain knowledge (nothing irrelevant included)
- Semantic correctness (exhibits expected functional behavior and non-functional qualities)
The first two dimensions are structurally verifiable—you can analyze code to check purity and completeness. The third requires domain expertise: does this module actually do what the business needs? Does it meet performance, security, and reliability requirements?
High cohesion isn't about counting methods or measuring dependencies. It's about a module faithfully representing exactly one domain's knowledge—structurally and semantically. This reframes architecture from dependency management to knowledge organization.
Why This Matters
Once you see architecture as knowledge organization, familiar principles take on new meaning:
- SRP: don't mix knowledge from different domains in one class
- Information Hiding: hide volatile knowledge behind stable boundaries
- DIP: separate policy knowledge from mechanism knowledge
- Cohesion: how purely does this module represent one domain's knowledge?
The principles aren't arbitrary rules. They're all ways of saying: organize knowledge properly. IVP makes this explicit by grounding architectural decisions in domain knowledge structure—specifically, in which knowledge changes together (same change driver) versus independently (different change drivers).
Cohesion's Primacy: Why Coupling Is a Function of Cohesion
For years, I understood coupling and cohesion as two independent concerns:
- Minimize coupling — reduce dependencies between modules
- Maximize cohesion — ensure each module has focused purpose
These goals appeared separate but complementary. But as I worked through IVP's implications, I became convinced of something that had been hiding in plain sight: coupling is not an independent concern—it's a consequence of how we organize knowledge. Problematic coupling emerges when cohesion is violated.
The Traditional View
In the traditional view, you might analyze a system and find:
- Module A has high cohesion (good)
- Module B has high cohesion (good)
- But A and B are tightly coupled (bad)
This framing suggests coupling and cohesion are orthogonal dimensions. You could have any combination: high cohesion with high coupling, low cohesion with low coupling, etc.
The Insight
Consider what "tight coupling" actually means: changes to A force changes to B even when B's core responsibility hasn't changed. But why would B need to change for A's reasons?
The answer, I realized, is straightforward: B contains elements that vary for A's change driver.
If B were purely cohesive—containing only elements governed by B's change driver—then A's changes couldn't affect it. B would only change for B's reasons. The coupling between A and B exists precisely because B is impure: it contains knowledge that belongs to A's domain.
Coupling as Impurity Made Visible
Every instance of "problematic coupling" can be traced to a cohesion violation:
| Observed Coupling | Underlying Cohesion Problem |
|---|---|
| A depends on B's internals | A contains knowledge that should be in B (A is impure) |
| Changes to A ripple to B | B contains knowledge governed by A's driver (B is impure) |
| A and B must deploy together | Both contain knowledge from a shared driver that should be extracted |
| Circular dependency A ↔ B | Both are impure—each contains elements belonging to the other |
The coupling is not the disease; it's the symptom. The disease is impurity—modules containing knowledge that doesn't belong to their change driver.
Practical Consequences
This understanding has several practical consequences:
1. Focus on cohesion, not coupling. If you maximize cohesion (complete and pure embodiment of each change driver's knowledge), minimal coupling follows automatically. You don't need to "reduce coupling" as a separate activity.
2. Coupling analysis reveals cohesion problems. When you observe tight coupling, don't ask "how do I decouple these?" Ask "which module contains knowledge that doesn't belong to it?" The coupling tells you where to look; the fix is purifying the impure module.
3. Decoupling without purification is futile. Adding interfaces, abstractions, or indirection between coupled modules doesn't fix the underlying problem. The coupling will reassert itself because the impure module still contains foreign knowledge. You've hidden the symptom without treating the disease.
4. Coupling severity indicates impurity degree. The more severe the coupling (the more changes that ripple), the more foreign knowledge a module contains. Coupling is a measure of impurity.
The Mathematical Relationship
The Knowledge Theorem establishes that maximal cohesion equals complete and pure knowledge embodiment. This means:
- Maximal cohesion → module contains exactly its change driver's knowledge, nothing more, nothing less
- Pure module → contains no knowledge governed by other change drivers
- No foreign knowledge → cannot be affected by other change drivers
- Cannot be affected → no coupling to those other drivers
The implication is direct: a module with maximal cohesion has minimal coupling by construction. You don't achieve minimal coupling by "removing dependencies." You achieve it by ensuring each module embodies exactly one change driver's knowledge.
Cohesion First, Always
This is why IVP-1 ("isolate divergent concerns") and IVP-2 ("unify by single purpose") are both statements about cohesion:
- IVP-1 prevents impurity by forbidding foreign knowledge
- IVP-2 ensures completeness by requiring related knowledge to stay together
Both directives target cohesion. Neither mentions coupling directly. Yet IVP-compliant structure has minimal coupling as a consequence.
This suggests an inversion of the traditional emphasis. Rather than treating coupling as a first-order concern to be actively managed, we might better focus on cohesion. Coupling is a second-order effect—a consequence of how we organize knowledge. Get cohesion right, and coupling tends to minimize naturally.
Classic Design Principles (1960s–1990s)
The following foundational principles emerged from early software engineering research. Each addresses a specific aspect of software structure, and each finds its explanation in IVP.
Separation of Concerns (Dijkstra)
What Dijkstra Actually Said (1974)
"Let me try to explain to you, what to my taste is characteristic for all intelligent thinking. It is, that one is willing to study in depth an aspect of one's subject matter in isolation for the sake of its own consistency, all the time knowing that one is occupying oneself only with one of the aspects."
— Dijkstra, "On the Role of Scientific Thought" (EWD447)
Relevant Change Drivers
SoC applies whenever you have multiple independent dimensions of variation:
-
γ_Avs.γ_Bvs.γ_Cwhere each represents a distinct aspect of the system - Technical layers (presentation, logic, persistence) often have independent drivers
- Domain modules often have independent drivers (different business stakeholders)
- Pipeline stages may have independent drivers (different processing concerns)
The Problem with SoC
SoC provides no criterion for what constitutes a "concern" or "aspect." Two architects can look at the same system and identify completely different concerns:
- One sees "data access" and "business logic" as separate concerns
- Another sees "user management" and "order processing" as the concerns
- A third sees "validation," "transformation," and "persistence"
Who's right? SoC provides no way to decide. It assumes you already know what the concerns are—but identifying concerns is often the hard part. As Dan North observed, "any non-trivial code can have any number of reasons to change"—so which decomposition is correct?
How IVP addresses this: Concerns are defined by change drivers. A concern is a set of elements that vary for the same cause. This provides a more concrete criterion: analyze your version control history, identify who requests changes and why, map out the causal structure. The concerns emerge from domain reality rather than relying solely on architectural intuition.
Relationship to IVP: IVP Operationalizes SoC
SoC and IVP share the same insight but operate at different levels of specificity:
| SoC | IVP |
|---|---|
| "Separate different aspects" | "Separate elements that vary for different causes" |
| Which aspects? Why? → Intuition | Which elements vary for different causes? → Causal analysis |
What IVP Adds
SoC leaves critical questions unanswered:
- What constitutes an "aspect"? IVP answers: aspects are defined by shared change drivers
- When should aspects be unified? SoC focuses only on separation; IVP provides the complementary directive (IVP-2)
- How to measure separation quality? SoC is qualitative; IVP enables measurement through cohesion metrics tied to change drivers
In practice, this means: instead of relying on intuition about what constitutes a "concern," analyze what actually changes together in your version control history. Concerns are defined by causal reality, not abstract categorization.
Information Hiding (Parnas)
What Parnas Actually Said (1972)
"We propose instead that one begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others."
Note: Parnas did not mention "stable interfaces." That phrase, while commonly associated with Information Hiding, is a later community interpretation—not Parnas's words.
Relevant Change Drivers
Information Hiding addresses the boundary between:
-
γ_client(what clients need) vs.γ_impl(how it's achieved internally) - The interface is stable; the implementation is volatile
- Hiding is warranted when
γ_implis likely to change independently ofγ_client
The Core Idea
The criterion is probabilistic: identify decisions "likely to change," then hide them. This requires prediction—you must judge what's likely to change. It's an economic decision because hiding has costs (abstraction overhead), and you only pay those costs where the probability of change justifies them.
Note: while this sounds like DIP, they're distinct principles. Information Hiding is probabilistic—it asks "what's likely to change?" and requires prediction. DIP is structural—it prescribes dependency direction without asking about probability. DIP says "always depend on abstractions"; Information Hiding says "hide what's likely to change." The structural result may look similar, but the reasoning differs.
Relationship to IVP: Different Concerns
Information Hiding and IVP address different questions:
| Information Hiding | IVP |
|---|---|
| Which decisions should we hide? | How should we structure if we decide to separate? |
| Based on probability ("likely to change") | Based on causal structure (independent change drivers) |
| Economic/predictive judgment | Structural/design guidance |
IVP tells you how to separate concerns correctly. If you decide to separate client needs from implementation details, IVP-1 tells you how: elements varying for γ_client go in one module, elements varying for γ_implementation go in another.
Information Hiding tells you whether to bother. Parnas's criterion is probabilistic: hide decisions "likely to change." This is an economic judgment—separation has costs (abstraction overhead, indirection complexity). You only pay those costs where the benefit (isolation from likely changes) justifies them.
The Architect's Two-Step Decision
IVP provides the structural analysis: "These concerns have independent change drivers. If we separate them, here's how to do it correctly."
Economic judgment decides whether to apply it: "Is this separation worth it? How likely is change? What's the cost of the abstraction? What's the cost of not separating if change occurs?"
IVP doesn't make the economic decision for you. But it does three critical things:
- Clarifies what you're deciding about: Which change drivers exist? Which are independent?
- Ensures correct boundaries: If you choose to separate, IVP tells you exactly where the lines should go
- Makes tradeoffs explicit: When you choose not to separate independent concerns, IVP tells you precisely what coupling you're accepting—which future changes will ripple across boundaries
This last point is crucial. Without IVP, you might skip a separation and not understand the cost. With IVP, you know: "I'm coupling γ_pricing and γ_compliance in this module. If either changes independently, I'll pay the price." That's an informed tradeoff, not a blind one.
What IVP Adds
Parnas provided the economic criterion ("likely to change") but not a systematic way to identify what the independent concerns are or how to separate them correctly. IVP fills this gap:
- Identifies change drivers: Who or what causes this code to change?
- Reveals independence: Are these change drivers independent?
- Guides the structure: If separating, ensure elements for each driver are isolated
The practical approach: use IVP to map out the change drivers and their independence relationships. Then make informed economic decisions about which separations to invest in. You might identify ten potential separations but only implement three—the ones where change is likely enough to justify the cost. IVP ensures those three are done correctly.
DRY (Don't Repeat Yourself)
What Hunt & Thomas Said (1999)
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system."
— Hunt & Thomas, The Pragmatic Programmer
Relevant Change Drivers
DRY concerns arise when code appears similar but may represent different knowledge:
-
γ_Avs.γ_B— two pieces of code look identical but change for different reasons - Same change driver → same knowledge → extract (IVP-2)
- Different change drivers → different knowledge → keep separate despite similarity (IVP-1)
The Problem with DRY
DRY itself is sound—it speaks of knowledge, not code. But in practice, developers often apply DRY based on structural similarity rather than knowledge identity. Two pieces of code can look identical yet represent different knowledge:
- Email validation in user registration
- Email validation in newsletter signup
These might have identical implementations today, but they represent different business decisions. Marketing might relax newsletter validation while security tightens user registration validation. They look the same but are different knowledge.
DRY provides no criterion for distinguishing "same knowledge" from "coincidentally similar code." Without such a criterion, teams extract "shared" code, then discover that changes for one use case break the other.
Additional problems with DRY in practice:
Premature abstraction: Developers extract patterns before understanding how components will diverge. As Kent C. Dodds advocates with "AHA Programming" (Avoid Hasty Abstractions), it's cheaper to wait for a pattern to emerge than to undo a bad abstraction.
Hidden coupling: DRY'd abstractions become "optimized for keeping all components the same"—they're coupled together with no affordances for elements to evolve in diverging directions.
Increased complexity: Reading DRY'd code requires jumping between abstraction layers, maintaining mental state across indirections. Linear, "duplicated" code is often easier to understand.
The sunk cost trap: Once engineers invest in an abstraction, they tend to continue iterating on it rather than recognizing when it's the wrong abstraction.
How IVP addresses this: IVP provides the missing criterion. Same knowledge = same change driver. If two pieces of code change for the same reason (same authority, same business rule), they represent the same knowledge—extract them. If they change for different reasons, they're different knowledge despite structural similarity—keep them separate. This prevents premature abstraction by requiring causal analysis before extraction.
Relationship to IVP: Context-Dependent
DRY's relationship to IVP is context-dependent: duplication sometimes violates IVP, sometimes satisfies it.
Case 1: True Duplication (DRY and IVP agree)
Email validation in both user registration and contact form:
- Both implement the same rule
- Both change for the same cause (email format requirements)
- IVP-2 applies: extract to shared module
Case 2: Accidental Similarity (IVP overrides DRY)
Order total calculation and inventory value calculation:
- Both sum item values
- Order total changes for
γ_pricing(pricing rules) - Inventory value changes for
γ_accounting(cost accounting rules) -
γ_pricing ⊥ γ_accounting(independent) - IVP-1 applies: keep separate despite structural similarity
The DRY Trap
Blindly applying DRY to accidental similarity creates a shared utility. When pricing rules evolve, the shared utility changes, forcing inventory calculations to revalidate despite no business reason—violating IVP-1.
What IVP Adds
DRY observes that duplication is often problematic but doesn't explain why or when. IVP provides the criterion:
- If duplication represents the same knowledge varying for the same cause: eliminate it (DRY and IVP-2 agree)
- If duplication represents coincidentally similar code varying for different causes: preserve it (IVP-1 overrides DRY)
The question to ask before extracting "duplicate" code: Do these pieces change for the same reason? If not, the apparent duplication is actually appropriate separation.
KISS (Keep It Simple, Stupid)
The Principle
KISS is a design principle originating from the U.S. Navy (1960s). Unlike the other principles, it has no single canonical statement—it's a folk principle advocating simplicity.
Relevant Change Drivers
Complexity concerns arise when independent change drivers are accidentally coupled:
-
γ_Aentangled withγ_Bwhereγ_A ⊥ γ_B— accidental coupling - Cross-cutting concerns scattered across multiple modules
- Essential coupling (
γ_Awithγ_A) is necessary; accidental coupling (γ_Awithγ_B) is unnecessary complexity
The Problem with KISS
KISS provides no criterion for what counts as "simple" or "unnecessary." Every developer thinks their solution is simple; complexity is always justified by some rationale:
- "We need this abstraction for testability"
- "This pattern makes future changes easier"
- "This indirection enables flexibility"
Without an objective measure, KISS becomes a rhetorical weapon: "Your solution is too complex" really means "I don't understand it" or "I would have done it differently." Two developers can disagree about simplicity with no way to resolve the dispute.
How IVP addresses this: IVP offers a more precise definition: unnecessary complexity is accidental coupling—coupling between elements that vary for independent causes. While determining independence still requires analysis, this gives you something concrete to examine: do these things change for the same reason or different reasons? Coupling things that change independently is unnecessary complexity; coupling things that change together is essential structure.
Relationship to IVP: IVP Operationalizes KISS
KISS identifies the goal; IVP provides the method.
Accidental vs. Essential Complexity (Brooks):
- Essential complexity: inherent to the problem
- Accidental complexity: arising from the solution
IVP provides precise definitions:
- Essential coupling: elements coupled because they vary for the same cause (IVP-2 mandates this)
- Accidental coupling: elements coupled despite varying for different causes (IVP-1 violation)
Accidental coupling creates accidental complexity. IVP-1 eliminates accidental coupling, thereby minimizing accidental complexity—exactly what KISS prescribes.
What IVP Adds
KISS is vague: what's "unnecessary"? IVP makes it precise: unnecessary complexity arises from accidental coupling—coupling elements that vary for independent causes.
When evaluating complexity, the useful question is: Is this coupling essential (things that genuinely change together) or accidental (independent concerns entangled)?
Law of Demeter (Principle of Least Knowledge)
What Lieberherr Said (1989)
"A method should only call methods of: (1) itself, (2) its parameters, (3) objects it creates, (4) its direct components."
— Lieberherr et al., "Assuring Good Style for Object-Oriented Programs"
Informally: "Don't talk to strangers."
Relevant Change Drivers
LoD concerns arise in chains that traverse independently-varying structures:
-
γ_A→γ_B→γ_C— each link may have an independent change driver - The more independent drivers in a chain, the more fragile the coupling
- Train wrecks couple the client to
nindependent sources of change
The Problem with LoD
LoD is often called the "Suggestion of Demeter" because it's so easy to violate and hard to follow strictly. Several issues arise:
Delegation can be a false solution: Using wrapper methods to hide violations removes visible evidence but doesn't actually decouple the code—it just hides tight coupling.
Strict adherence leads to "wrapper hell": Numerous small wrapper methods can introduce excessive indirection, making code harder to read than the original "train wreck."
Data structures are often exempt: When accessing pure data structures (DTOs, configuration objects), strict LoD application adds ceremony without benefit.
No clear boundary: How many dots are too many? LoD provides no criterion for when a chain becomes problematic.
How IVP addresses this: IVP explains why train wrecks are problematic: each link in the chain is a potential independent change driver. Count the independent things that could change and break the chain. If order.getCustomer().getAddress().getZipCode() involves three independently changing structures, that's the problem—not the dot count itself. Data structure chains where everything changes together are less concerning.
Relationship to IVP: Bidirectional Equivalence
LoD is IVP-1 applied to method invocation scope.
Train wreck: order.getCustomer().getAddress().getZipCode()
Client C now varies for three independent causes:
-
γ_C: client's own requirements -
γ_Customer: Customer structure changes -
γ_Address: Address structure changes
This violates IVP-1. The fix (delegate to order.getCustomerZipCode()) is IVP-1 in action.
What IVP Adds
LoD gives the rule; IVP explains why: train wrecks couple clients to multiple independent variation sources.
When you see a long method chain, the relevant question is: How many independent things could change and break this chain? Each independent change source is a reason to encapsulate.
Principle of Least Surprise
The Principle
The Principle of Least Surprise (also called Principle of Least Astonishment) is a folk principle with no single canonical source. It states that system behavior should match user expectations.
Relevant Change Drivers
PoLS doesn't address change drivers—it addresses human cognition:
- User expectations, domain conventions, platform idioms
- These aren't change drivers in the IVP sense
- PoLS is orthogonal to IVP: understandability vs. evolvability
The Problem with PoLS
PoLS is subjective and non-actionable. "Least surprise" depends entirely on whose expectations:
- A novice expects different behavior than an expert
- A user from one domain brings different assumptions than another
- Even the same person's expectations change over time
How do you objectively apply PoLS? You can't. It provides no method, no criterion, no way to resolve disagreements. Two developers can disagree about what's "surprising" with no way to arbitrate.
IVP doesn't address this: IVP and PoLS solve different problems. IVP provides objective criteria for structural organization (evolvability). PoLS addresses human comprehension (understandability). IVP can't make PoLS objective—but it doesn't need to. They're orthogonal concerns.
Relationship to IVP: Orthogonal
PoLS and IVP address fundamentally different concerns:
- PoLS: Human comprehension—how understandable is the system? (subjective)
- IVP: Structural evolution—how independently can concerns vary? (objective)
Neither implies the other:
- An IVP-compliant system can violate PoLS (surprising behavior despite good structure)
- A PoLS-compliant system can violate IVP (unsurprising but tightly coupled)
Practical note: PoLS is a reasonable goal but not a principle in any actionable sense. IVP, by contrast, provides criteria grounded in causal analysis of change drivers—though applying IVP still requires domain knowledge and judgment.
Conway's Law
What Conway Said (1968)
"Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations."
— Melvin Conway, "How Do Committees Invent?"
Relevant Change Drivers
Conway's Law maps organizational structure to architectural structure:
- Team boundaries become module boundaries
- The question: do team boundaries align with genuine domain change drivers?
- If
γ_teammatchesγ_domain, Conway alignment is beneficial - If
γ_team≠γ_domain, Conway produces IVP-violating structure
The Problem with Conway's Law
Conway's Law is an observation, not a prescription—yet it's often treated as guidance. Several issues arise:
It doesn't guarantee quality: Even if architecture matches organization, the result can still be poorly designed. Mirroring doesn't imply correctness.
It limits innovation: Current team structures constrain the solution space. As Conway himself noted, "there is a class of design alternatives which cannot be effectively pursued" because necessary communication paths don't exist.
Path dependency traps: Existing systems impose structure on organizations. You're constrained by how you got here, not by what's optimal now.
Turf wars and silos: Teams become protective of their domains, leading to segmented knowledge and resistance to collaboration.
Inverse Conway is hard: Reorganizing teams doesn't immediately fix embedded architecture. The existing code "pushes back" against new structures.
How IVP addresses this: IVP provides the criterion for when Conway alignment is beneficial. If team boundaries match change driver boundaries, Conway's Law produces IVP-compliant structure—embrace it. If organizational structure doesn't align with change drivers (e.g., frontend/backend split when features span both), Conway's Law produces IVP-violating structure—resist it or restructure teams.
Relationship to IVP: IVP Explains When Conway's Law Is Beneficial
Conway's Law is an observation, not a prescription. IVP explains why it holds and when to embrace it:
When to embrace: If teams represent independent change drivers (Team A owns payments changing for payment reasons; Team B owns inventory changing for inventory reasons), architectural boundaries should match team boundaries. Conway's Law producing IVP-compliant structure is beneficial.
When to resist: If organizational structure doesn't align with change drivers (frontend/backend split but features require changes to both), Conway's Law produces IVP-violating structure. Every feature requires coordination.
Practical improvement: Ask: Do our team boundaries match our change driver boundaries? If not, consider restructuring teams or accepting the coordination overhead.
Tell, Don't Ask
The Principle
"Procedural code gets information then makes decisions. Object-oriented code tells objects to do things."
— Attributed to various OO practitioners
Rather than querying an object's state and acting on it externally, tell the object to perform the action itself.
Relevant Change Drivers
Tell, Don't Ask concerns data/behavior co-location:
-
γ_data+γ_behavior— same driver? Co-locate (IVP-2) -
γ_datavs.γ_consumer— different drivers? Querying is appropriate - The question: does the behavior change for the same reason as the data?
The Problem with Tell, Don't Ask
Tell, Don't Ask can be misused to eliminate all getters, even when querying is appropriate. Martin Fowler notes: "I've seen it encourage people to become GetterEradicators." Sometimes you genuinely need to ask an object for information—reporting, serialization, display logic.
How IVP addresses this: The issue isn't asking vs. telling—it's where the knowledge lives. If the logic operating on data should change for the same reasons as the data itself, co-locate them (IVP-2). If query results are consumed by code with a different change driver (e.g., UI rendering), separation is appropriate.
Relationship to IVP: IVP Refines Tell, Don't Ask
Tell, Don't Ask is about co-locating behavior with data. IVP explains when this matters: when behavior and data share a change driver. External queries are fine when the consumer varies independently.
Command Query Separation (CQS)
What Meyer Said (1988)
"Every method should either be a command that performs an action, or a query that returns data to the caller, but not both."
— Bertrand Meyer, Object-Oriented Software Construction
Asking a question should not change the answer.
Relevant Change Drivers
CQS addresses predictability rather than change drivers per se:
-
γ_queryvs.γ_command— may or may not be independent - CQS is about preventing hidden coupling, not organizing known variation
- Orthogonal to IVP: CQS ensures predictability; IVP ensures evolvability
The Problem with CQS
CQS introduces complexity for operations that naturally combine both—popping a stack, generating a unique ID, acquiring a lock. Strict adherence requires awkward workarounds. CQS also complicates concurrent code where atomic query-then-command operations are necessary.
How IVP addresses this: CQS is about predictability, not change drivers. IVP doesn't directly address CQS. However, CQS violations often create hidden state changes—accidental coupling between querying code and state. When queries have side effects, consumers are coupled to variation they didn't expect.
Relationship to IVP: Complementary
CQS ensures predictability; IVP ensures evolvability. Both reduce accidental coupling, but through different mechanisms. CQS prevents queries from introducing unexpected variation; IVP organizes variation that legitimately exists.
XP and Agile Principles (1990s–2000s)
Extreme Programming and the Agile movement brought a focus on evolutionary design, simplicity, and responding to change. These principles complement IVP's emphasis on change drivers.
YAGNI (You Aren't Gonna Need It)
What Beck & Jeffries Said (1999)
"Always implement things when you actually need them, never when you just foresee that you need them."
— Ron Jeffries, XP co-founder
YAGNI emerged from Extreme Programming (XP), coined by Kent Beck.
Relevant Change Drivers
YAGNI concerns hypothetical vs. actual change drivers:
-
γ_actual— change driver that exists now, requires handling -
γ_hypothetical— speculated future driver, may never materialize - Abstraction is premature if only one actual driver exists
The Problem with YAGNI
YAGNI provides no criterion for distinguishing "needed now" from "needed soon." Developers disagree about what's speculative:
- "We'll definitely need this abstraction when we add the second payment provider"
- "This validation layer will be necessary when requirements change"
Without a framework, YAGNI becomes a rhetorical tool—some invoke it to block useful preparation, others ignore it to justify over-engineering.
How IVP addresses this: Build abstractions when you identify actual independent change drivers. If you have one payment provider and no concrete second one, there's one change driver—no abstraction needed. When a second provider appears (a new change driver), then IVP-1 mandates separation. IVP grounds YAGNI in causal reality rather than speculation.
Relationship to IVP: IVP Operationalizes YAGNI
YAGNI says "don't build it until you need it." IVP says "don't separate until you have independent change drivers." Both resist premature structure, but IVP provides the criterion: genuine independent variation, not hypothetical future variation.
Four Rules of Simple Design (Kent Beck)
Kent Beck developed these rules while creating Extreme Programming in the late 1990s. The rules are in priority order.
- Passes the tests
- Reveals intention
- No duplication
- Fewest elements — Kent Beck, Extreme Programming Explained
Rule 1: Passes the Tests
The Rule: The code must work correctly—all tests pass.
Relevant Change Drivers: None directly. This is a correctness constraint, not a structural principle. Tests verify functional behavior, not evolvability.
The Problem: "Passes tests" is necessary but not sufficient. Code can pass all tests while being poorly structured, tightly coupled, and hard to evolve.
Relationship to IVP: Orthogonal. IVP addresses evolvability; passing tests addresses correctness. Both are necessary; neither implies the other.
Rule 2: Reveals Intention
The Rule: Code should clearly communicate its purpose. Names, structure, and organization should make the code's intent obvious.
Relevant Change Drivers: This addresses human cognition, not change drivers. Like PoLS, it's about understandability rather than evolvability.
The Problem: "Intention" is subjective—intention to whom? A domain expert reads code differently than a junior developer. The principle provides no criterion for resolving disagreements about clarity.
Relationship to IVP: Orthogonal. IVP addresses evolvability; revealing intention addresses understandability. IVP-compliant code can be unclear; clear code can violate IVP. Both dimensions matter independently.
Rule 3: No Duplication
The Rule: Each piece of knowledge should appear exactly once. Eliminate redundant code.
Relevant Change Drivers:
-
γ_Avs.γ_B— two pieces of code look identical but may change for different reasons - Same change driver → same knowledge → extract (IVP-2)
- Different change drivers → different knowledge → keep separate despite similarity (IVP-1)
The Problem: Same as DRY—when is duplication "same knowledge" vs. "coincidentally similar code"? Without a criterion, teams extract "shared" code that later needs to diverge.
How IVP addresses this: Same knowledge = same change driver. If two pieces of code would change for the same reason (same authority, same business rule), they represent the same knowledge—eliminate duplication. If they'd change for different reasons, they're different knowledge despite structural similarity—keep them separate.
Relationship to IVP: IVP-2 provides the criterion. "No duplication" is correct when duplication represents the same change driver.
Rule 4: Fewest Elements
The Rule: Subject to the above rules, minimize the number of classes, methods, and other structural elements.
Relevant Change Drivers: This rule constrains structure—don't add elements beyond what's needed.
The Problem: "Fewest elements" can conflict with "reveals intention"—sometimes more classes with clearer names are better than fewer obscure ones. The priority ordering (intention > fewest) helps but doesn't resolve all tensions.
How IVP addresses this: Elements should map to change drivers. If you have three independent concerns, three classes is appropriate—not "too many." If you have one concern, one class is appropriate—additional structure is accidental complexity.
Relationship to IVP: IVP explains when elements are "necessary"—one module per change driver. "Fewest elements" means don't create structure beyond what change drivers require.
The Four Rules Together
The rules are valuable but incomplete without a unifying criterion. IVP provides that criterion for rules 3 and 4:
| Rule | IVP Relationship |
|---|---|
| Passes tests | Orthogonal (correctness, not evolvability) |
| Reveals intention | Orthogonal (understandability, not evolvability) |
| No duplication | IVP-2 provides the criterion (same change driver = same knowledge) |
| Fewest elements | IVP defines "necessary"—one per change driver |
Once and Only Once (OAOO)
The Principle
"Say everything once and only once."
— Kent Beck
This is Beck's formulation of DRY, emphasizing that each piece of knowledge should have exactly one authoritative representation.
Relevant Change Drivers
Same as DRY:
-
γ_Agoverns knowledge K → K should appear once, in the module forγ_A - Multiple representations of K create synchronization problems when
γ_Atriggers change
The Problem with OAOO
Same as DRY: What counts as "the same thing"? Two code fragments may look identical but represent different knowledge if they change for different reasons.
How IVP addresses this: "Same thing" = governed by the same change driver. If two pieces of code would change for the same reason (same authority, same business rule), they're the same knowledge—say it once. If they'd change independently, they're different knowledge despite structural similarity.
Relationship to IVP: IVP Operationalizes OAOO
OAOO is DRY by another name. IVP provides the criterion: knowledge identity is determined by change driver identity.
Agile Manifesto Design Principles
The Agile Manifesto (2001) was written by 17 software experts including Kent Beck, Martin Fowler, and Robert C. Martin. While primarily about process, three of its 12 principles directly address software design.
The Principles
"Continuous attention to technical excellence and good design enhances agility."
— Agile Manifesto, Principle 9"Simplicity—the art of maximizing the amount of work not done—is essential."
— Agile Manifesto, Principle 10"The best architectures, requirements, and designs emerge from self-organizing teams."
— Agile Manifesto, Principle 11
Relevant Change Drivers
The Agile Manifesto addresses the meta-level of how change drivers are discovered:
- Teams closest to the work understand change drivers best
- Design must accommodate change (agility requires evolvability)
- Simplicity reduces the number of elements that can be coupled
The Problem with the Agile Design Principles
These principles are aspirational but not actionable:
"Technical excellence and good design" is circular—what is good design? The principle assumes you already know.
"Simplicity" has the same issue as KISS—no objective criterion for what's simple or what work is unnecessary.
"Emerge from self-organizing teams" describes who makes decisions but not how to make them correctly.
How IVP addresses this: IVP provides the criterion these principles lack. "Good design" = IVP-compliant structure. "Simplicity" = minimal accidental coupling (no coupling between independent change drivers). "Emergent architecture" works because teams close to the domain understand actual change drivers—IVP tells them what to do with that knowledge.
Relationship to IVP: IVP Operationalizes
The Agile Manifesto recognizes that design must support change but doesn't specify how. IVP provides the operational definition:
- Technical excellence = modules with maximal cohesion (complete and pure knowledge embodiment)
- Simplicity = no accidental coupling between independent change drivers
- Emergent design = structure that reflects actual change drivers discovered during development
The reason "the best architectures emerge from self-organizing teams" is that those teams learn the actual change drivers through building. IVP tells them how to structure based on what they learn.
Systems-of-Systems Principles (1990s–2000s)
Systems-of-Systems (SoS) architecting addresses a different scale: large-scale systems composed of operationally and managerially independent constituent systems. Mark Maier's foundational 1998 paper "Architecting Principles for Systems-of-Systems" established four principles that remain influential in systems engineering.
What Defines a System-of-Systems?
Maier identified five characteristics distinguishing SoS from conventional systems:
- Operational Independence: Constituent systems operate independently and usefully on their own
- Managerial Independence: Constituent systems are managed independently
- Evolutionary Development: The SoS develops over time; constituent systems join and leave
- Emergent Behavior: The SoS exhibits capabilities beyond any individual constituent
- Geographic Distribution: Constituent systems are often physically distributed
These characteristics create unique architectural challenges: you cannot mandate compliance, you cannot control constituent system evolution, and the "architecture" exists primarily as communication standards rather than physical structure.
Maier's Four Architecting Principles
1. Stable Intermediate Forms
The Principle: Evolution must proceed through stable intermediate states. The architect must pay attention to intermediate steps in a planned evolution of the SoS.
Relevant Change Drivers:
-
γ_SoS— the overall system-of-systems capability -
γ_constituent₁,γ_constituent₂, ... — each constituent system's evolution - Evolution must maintain stability while individual constituents change
The Problem: SoS cannot be built monolithically—constituent systems already exist and evolve independently. A "big bang" integration approach fails because there's no stable state to operate from during transition.
How IVP addresses this: Each intermediate state must be IVP-compliant at its level. Constituent systems must maintain their cohesion (γ_constituentᵢ) while integrating with others. The SoS architecture must isolate constituent variation so that one system's evolution doesn't destabilize others.
Relationship to IVP: Stable Intermediate Forms is IVP applied to evolutionary architecture. Each intermediate must isolate independent constituent change drivers so evolution can proceed incrementally.
2. Policy Triage (Leverage Policy, Not Structure)
The Principle: Given limited influence over constituent system design, architects must carefully select strategic intervention points. The opportunities to influence constituent systems are limited; the points of influence must be chosen carefully.
Relevant Change Drivers:
-
γ_architecture— SoS-level architectural decisions -
γ_constituent— individual system implementation details - The SoS architect controls
γ_architecturebut notγ_constituent
The Problem: SoS architects cannot mandate internal structure of constituent systems. Traditional architectural control (specifying interfaces, enforcing patterns) is unavailable. What levers remain?
How IVP addresses this: Focus on policies (what must be achieved) rather than structure (how to achieve it). Policies define change driver boundaries without prescribing implementation. A constituent system can vary internally (γ_constituent) while adhering to policies that maintain SoS coherence (γ_architecture).
Relationship to IVP: Policy Triage is IVP-1 applied through policy rather than structure. It acknowledges that you cannot control all change drivers—only establish boundaries between the ones you influence and those you don't.
3. Leverage Interfaces Through Standards
The Principle: The architect must leverage the interfaces to realize the capability that the SoS is assembled to deliver. In most cases, the architecture of a system-of-systems is communications—the set of standards that allow meaningful communication among components.
Relevant Change Drivers:
-
γ_standard— the communication protocol/standard (controlled by SoS architect) -
γ_implementation— how each constituent implements the standard (independent per system) - Standards provide the stable abstraction; implementations vary independently
The Problem: With no control over constituent internals, the only leverage is at interfaces. But interfaces must be standardized to enable interoperability—hence standards become the architecture itself.
How IVP addresses this: Standards are the stable abstraction that isolates independent variation. Each constituent varies for its own reasons (γ_constituentᵢ); the standard ensures they can still communicate. This is DIP at the systems-of-systems scale: constituents depend on abstractions (standards), not on each other's implementations.
Relationship to IVP: Leveraging Interfaces is DIP/IVP-1 at the SoS scale. The standard isolates constituent change drivers from each other while enabling collaboration.
Example: The Internet's TCP/IP protocols are the classic example. No central authority controls how individual networks implement TCP/IP—only that they adhere to the standard. The architecture is the protocol suite.
4. Ensure Cooperation
The Principle: Collaboration between independent systems is only possible if system owners choose to collaborate. Incentivizing collaboration requires appreciation of motivations and non-technical aspects (commercial benefits, protection of IP).
Relevant Change Drivers:
-
γ_technical— technical requirements for integration -
γ_organizational— business, political, and economic factors affecting cooperation - Both must be addressed; technical architecture alone is insufficient
The Problem: Even with perfect standards, constituent systems won't join an SoS unless their owners perceive benefit. Technical architecture must align with organizational incentives.
How IVP addresses this: This principle extends IVP beyond pure technical concerns. γ_organizational (stakeholder incentives, business models, IP concerns) is as real a change driver as γ_technical. SoS architecture must account for both—technical structures that ignore organizational change drivers will fail regardless of their technical elegance.
Relationship to IVP: Ensure Cooperation acknowledges that change drivers include organizational and economic factors, not just technical ones. IVP applies to sociotechnical systems, not just code.
What IVP Adds to SoS Principles
Maier's principles were derived empirically from observing successful and failed systems-of-systems. IVP provides the theoretical foundation:
| SoS Principle | IVP Explanation |
|---|---|
| Stable Intermediate Forms | IVP-compliant structure at each evolutionary state |
| Policy Triage | IVP-1 via policy boundaries when structural control is unavailable |
| Leverage Interfaces | DIP/IVP-1 at the inter-system scale |
| Ensure Cooperation | Change drivers include organizational/economic factors |
The key insight: at the SoS scale, the "architecture" is not physical structure but communication standards. This is IVP taken to its logical extreme—when you cannot control implementations, you can only control the boundaries between them. Standards define those boundaries, isolating constituent change drivers while enabling emergent capabilities.
INCOSE Systems Engineering Principles
The International Council on Systems Engineering (INCOSE) published a formal set of 15 Systems Engineering Principles in 2022, representing 30+ years of accumulated wisdom. Several are directly relevant to IVP:
"Complex systems are engineered by complex organizations." This principle acknowledges Conway's Law at the systems level. IVP explains when this alignment is beneficial (team boundaries match change driver boundaries) versus problematic (organizational structure forces inappropriate coupling).
"SE has a holistic system view that includes system elements and their interactions." This is IVP-2 at the systems level—understanding not just elements but how they vary together.
"Decision quality depends on understanding the system, enabling systems, and interoperating systems." This resonates with the Knowledge Theorem: quality decisions require understanding the knowledge embodied in each system and how that knowledge varies.
Cloud-Native Principles (2010s)
The rise of cloud computing and microservices brought new architectural concerns. These principles address deployment and operational variation.
12-Factor App Methodology
The 12-Factor App methodology was developed by Adam Wiggins and the Heroku team circa 2011. It prescribes best practices for building cloud-native, scalable software-as-a-service applications.
The Factors
- Codebase: One codebase tracked in version control, many deploys
- Dependencies: Explicitly declare and isolate dependencies
- Config: Store config in the environment
- Backing Services: Treat backing services as attached resources
- Build, Release, Run: Strictly separate build and run stages
- Processes: Execute the app as stateless processes
- Port Binding: Export services via port binding
- Concurrency: Scale out via the process model
- Disposability: Maximize robustness with fast startup and graceful shutdown
- Dev/Prod Parity: Keep development, staging, and production as similar as possible
- Logs: Treat logs as event streams
- Admin Processes: Run admin/management tasks as one-off processes
Relevant Change Drivers
12-Factor explicitly separates infrastructure concerns from application logic:
-
γ_config— environment-specific configuration (Factor 3) -
γ_backing_service— database, queue, cache providers (Factor 4) -
γ_buildvs.γ_releasevs.γ_run— distinct lifecycle stages (Factor 5) -
γ_appvs.γ_environment— application code vs. deployment context (Factor 10)
The Problem with 12-Factor
12-Factor is prescriptive but not principled. It tells you what to do but not why:
No unifying rationale: Why these 12 factors? Why not 10 or 15? The factors appear as a list of best practices rather than derivations from a principle.
Implicit assumptions: The factors assume cloud deployment, statelessness, and horizontal scaling. They don't explain when these assumptions don't apply.
Scope limitation: 12-Factor addresses deployment architecture but says nothing about domain modeling, business logic organization, or code structure.
How IVP addresses this: IVP explains why these factors work. Each factor isolates a different change driver:
- Config (Factor 3) isolates
γ_environmentfromγ_app—environment changes shouldn't require code changes - Backing Services (Factor 4) isolates
γ_providerfromγ_app—switching databases shouldn't require code changes - Build/Release/Run (Factor 5) isolates
γ_buildfromγ_run—runtime changes shouldn't require rebuilding
Relationship to IVP: Domain-Specific IVP-1
12-Factor is IVP-1 applied to the cloud/operations domain:
| Factor | Change Driver Separation |
|---|---|
| Config |
γ_app ⊥ γ_environment
|
| Backing Services |
γ_app ⊥ γ_provider
|
| Build/Release/Run |
γ_build ⊥ γ_release ⊥ γ_run
|
| Dev/Prod Parity | Minimize γ_dev vs. γ_prod divergence |
| Processes | Statelessness isolates γ_instance₁ from γ_instance₂
|
12-Factor discovered these separations empirically by observing what causes production incidents: deployments that couple app code to environment, builds that couple to runtime, processes that couple to each other via shared state.
IVP explains the pattern: each factor prevents accidental coupling between independent change drivers in the operational domain.
GoF Principles
The Gang of Four (Design Patterns, 1994) articulated two foundational principles alongside their pattern catalog.
Relevant Change Drivers
GoF principles address implementation-level variation:
-
γ_interfacevs.γ_impl— contract vs. mechanism -
γ_parentvs.γ_child— inheritance couples these; are they truly dependent? - Composition allows
γ_Aandγ_Bto vary independently
Program to an Interface, Not an Implementation
What GoF Said (1994):
"Don't declare variables to be instances of particular concrete classes. Instead, commit only to an interface defined by an abstract class."
— Gamma, Helm, Johnson, Vlissides
The Problem: This is essentially DIP at the variable/type level. The same issue applies: when is the abstraction worthwhile? Adding interfaces everywhere creates ceremony without benefit.
How IVP addresses this: Program to an interface when the implementation might vary independently. If you'll only ever have one implementation (single change driver), the interface adds indirection without isolation benefit. Multiple potential implementations = multiple change drivers = interface warranted.
Favor Composition Over Inheritance
What GoF Said (1994):
"Favor object composition over class inheritance."
— Gamma, Helm, Johnson, Vlissides
The Problem: GoF observed that "designers overuse inheritance" and that "inheritance breaks encapsulation." But when is inheritance appropriate?
How IVP addresses this: Inheritance creates tight coupling—subclasses vary for both their own causes and their parent's causes. Composition allows independent variation. Use inheritance when parent and child genuinely share a change driver (they evolve together). Use composition when they might diverge.
SOLID Principles
Single Responsibility Principle (SRP)
What Martin Said:
"A class should have one, and only one, reason to change."
— Robert C. Martin, Agile Software Development (2003)
Relevant Change Drivers:
-
γ_Avs.γ_Bvs.γ_C— different stakeholders, different reasons to change - A class with multiple independent drivers violates SRP
- A class with one driver satisfies SRP
The Problem with SRP: What counts as "one reason to change"? This is famously vague. The same class can be described as having one responsibility ("handles user data") or multiple ("validates users, persists users, formats user display"). The granularity is undefined—SRP provides no way to determine when you've reached "one" responsibility.
How IVP addresses this: A "reason to change" is a change driver—an independently varying source of change. Change drivers are concrete: the pricing team, the compliance department, the infrastructure group, an external API. You can identify them by asking "who requests this change?" or "what causes this to need modification?" If the answer is multiple independent authorities or concerns, the class has multiple responsibilities.
Relationship: SRP is IVP applied at class granularity.
- If $|\Gamma(C)| > 1$ (class has multiple change drivers), SRP is violated
- If $|\Gamma(C)| = 1$ (class has single change driver), SRP is satisfied
What IVP Adds: SRP's "reason to change" is vague. IVP makes it precise: a change driver is an independently varying source of change to domain knowledge. Different actors, different business domains, different technical concerns—these are independent change drivers.
When evaluating whether a class has "one responsibility," ask: Who or what causes this code to change? If the answer is multiple independent actors or concerns, consider splitting the class.
Open/Closed Principle (OCP)
What Martin Said:
"Software entities should be open for extension, but closed for modification."
— Robert C. Martin, "The Open-Closed Principle" (1996)
Relevant Change Drivers:
-
γ_corevs.γ_variant₁,γ_variant₂, ... — stable core, volatile variations - OCP warranted when
γ_core ⊥ γ_variantᵢ(independent) - OCP overhead unjustified when everything changes together
The Problem with OCP: OCP provides no criterion for when to apply it. Making code "open for extension" requires upfront investment (abstractions, interfaces, plugin points). Applied everywhere, OCP leads to over-engineering. Applied nowhere, code becomes rigid. OCP tells you the goal but not when the cost is justified.
How IVP addresses this: Apply OCP when you identify independent change drivers. Core logic changes for cause γ_core; variations change for causes γ_v1, γ_v2, etc. If these are genuinely independent, OCP is warranted—the abstraction isolates independent variation. If they're not independent (everything changes together), OCP adds complexity without benefit.
Relationship: OCP is IVP-1 applied to stable/variant separation.
- Core functionality
Fchanges for causeγ_F - Variations
V₁, V₂, ...change for causesγ_V₁, γ_V₂, ... - These causes are independent:
γ_F ⊥ γ_Vᵢ
Applying IVP-1 separates stable abstraction from volatile implementations. Adding new implementations doesn't trigger γ_F, so the abstraction remains unchanged.
What IVP Adds: OCP explains the goal but not when to apply it. IVP provides the criterion: apply OCP when you identify independent variation between core logic and its variations. Not every piece of code needs extension points—only where independent change drivers exist.
Practical improvement: Before adding abstractions "for future extensibility," identify actual change drivers. Is there genuine independent variation? Or are you adding complexity for hypothetical future changes that may never occur?
Liskov Substitution Principle (LSP)
What Liskov & Wing Said (1994):
"Subtypes must be substitutable for their base types."
— Barbara Liskov & Jeannette Wing, "A Behavioral Notion of Subtyping"
Relevant Change Drivers:
-
γ_basevs.γ_derived— do they genuinely share a change driver? - LSP violation: client must handle
γ_derivedspecially, introducing accidental coupling - False unification: elements appear unified but vary for different causes
The Problem with LSP: LSP is difficult to validate because you must inspect clients to identify problems—a model viewed in isolation cannot be meaningfully validated. Classic violations (Square/Rectangle, Penguin/Bird) show that "is-a" relationships in the domain don't always translate to valid inheritance. Trying to anticipate all client assumptions yields needless complexity.
How IVP addresses this: LSP violations create false unification—elements that appear unified actually vary for different causes from the client's perspective. When a subtype requires special-case handling, the client now varies for an additional cause (accommodating that subtype's behavior). IVP-2 requires genuine dependent variation; LSP ensures the abstraction doesn't lie about this.
Relationship: LSP is IVP-2's behavioral constraint for type hierarchies.
If derived type D violates base type B's contract, client K must add special-case logic:
if (instance is D) handleDifferently();
Now K varies for two independent causes:
-
γ_K: client's core responsibility -
γ_D: accommodatingD's non-substitutable behavior
This violates IVP-1.
What IVP Adds: LSP is often stated as a contract requirement. IVP explains why it matters: LSP ensures elements unified by abstraction genuinely vary for the same cause from the client's perspective. Without LSP, the abstraction is false unification—elements appear unified but actually vary for different causes.
Practical improvement: When designing inheritance hierarchies, ask: Do all subtypes genuinely change for the same reasons as the base type? If not, you probably have a misuse of inheritance.
Interface Segregation Principle (ISP)
What Martin Said:
"Clients should not be forced to depend on interfaces they do not use."
— Robert C. Martin, "The Interface Segregation Principle" (1996)
Relevant Change Drivers:
-
γ_client₁vs.γ_client₂— different clients with different needs - Fat interface forces
γ_client₂to depend on changes driven byγ_client₁ - Segregate when clients have independent change drivers
The Problem with ISP: ISP tells you to segregate but not how to determine the boundaries. Should a UserService interface be split into UserReader and UserWriter? Or UserAuthenticator, UserProfileManager, and UserPreferencesManager? ISP provides no criterion for the right granularity.
How IVP addresses this: Segregate by change driver. Group methods that change for the same cause into one interface. If authentication methods change for security reasons while profile methods change for UX reasons, those are different change drivers—segregate them. The boundaries emerge from causal analysis, not arbitrary decomposition.
Relationship: ISP is IVP-1 applied to interface design—but note how it differs from Information Hiding/DIP.
ISP vs. Information Hiding/DIP:
| Information Hiding / DIP | ISP |
|---|---|
| Separate contract from implementation | Separate contracts from each other |
| Vertical: client ↔ implementation | Horizontal: client₁ ↔ client₂ |
| One contract hiding one implementation | Multiple contracts for same implementation |
Information Hiding/DIP is about hiding how you work behind what clients can expect. ISP is about not forcing a single fat contract on clients with different needs—it's about having multiple role-specific contracts when clients have independent change drivers.
Fat interface I with methods for clients K₁ and K₂:
- Methods for
K₁change for causeγ₁ - Methods for
K₂change for causeγ₂ - If
γ₁ ⊥ γ₂, the fat interface couplesK₂to changes irrelevant to it
IVP-1 directs: segregate by variation cause.
What IVP Adds: ISP tells you to segregate interfaces but not how to determine segregation boundaries. IVP provides the criterion: group methods that change for the same cause.
Practical improvement: When designing interfaces, ask: Which clients use which methods? If different clients use different method subsets and those clients represent independent concerns, segregate.
Dependency Inversion Principle (DIP)
What Martin Said:
"High-level modules should not depend on low-level modules. Both should depend on abstractions."
— Robert C. Martin, "The Dependency Inversion Principle" (1996)
Relevant Change Drivers:
-
γ_high(high-level policy) vs.γ_low(low-level mechanism) - DIP warranted when
γ_high ⊥ γ_low(independent) - DIP overhead unjustified when layers always change together
The Problem with DIP: DIP is often applied dogmatically—"always add an interface." But abstractions have costs: indirection, cognitive overhead, maintenance burden. DIP provides no criterion for when inversion is worthwhile versus when it's over-engineering.
How IVP addresses this: Apply DIP when high-level and low-level modules have independent change drivers. Business policy (γ_policy) and infrastructure (γ_infrastructure) typically change for different reasons—DIP is warranted. But if two layers always change together (they're not actually independent), the abstraction adds ceremony without isolation benefit.
Relationship: DIP is IVP-1 applied to layered class dependencies.
DIP differs from Information Hiding:
| Information Hiding | DIP |
|---|---|
| Probabilistic: "hide what's likely to change" | Structural: "high-level depends on abstractions" |
| Economic judgment required | Prescriptive rule |
| Module-level principle | Class-level in layered architecture |
Information Hiding asks "what's likely to change?" and makes an economic decision. DIP doesn't ask—it prescribes a dependency direction. The structural outcomes may look similar, but the reasoning differs.
How IVP relates: DIP implicitly recognizes that high-level policy and low-level implementation have independent change drivers. Business rules (γ_policy) change for business reasons; infrastructure (γ_infrastructure) changes for technical reasons. IVP-1 mandates their separation. DIP prescribes how to achieve it in OO: depend on abstractions.
What IVP Adds: DIP tells you to always invert dependencies across layers. IVP explains when this is actually necessary: only when the layers have independent change drivers. Not all dependencies need inversion—only those crossing change driver boundaries.
Practical improvement: Before introducing an abstraction layer, verify that the things on either side genuinely change independently. If they always change together, the abstraction adds complexity without benefit.
Package Architecture Principles
The Package Principles suffer from the same fundamental issue as the SOLID principles: they describe what to achieve but provide no criterion for how to identify the right boundaries. IVP provides that criterion—change drivers—making these principles actionable.
Relevant Change Drivers
Package principles organize change at the deployment/release granularity:
-
γ_package_A,γ_package_B— classes that change together belong in same package - Packages with slow-changing drivers should be stable
- Packages with fast-changing drivers should be volatile
Common Closure Principle (CCP)
What Martin Said:
"The classes in a package should be closed together against the same kinds of changes."
— Robert C. Martin, Agile Software Development (2003)
The Problem with CCP: What counts as "the same kind of change"? Without a definition, two architects can disagree about whether classes belong together.
How IVP addresses this: CCP is literally IVP-2 at package granularity. "Same kind of change" = same change driver. Classes governed by the same authority, same business rule, or same external dependency belong together.
Relationship: CCP is literally IVP-2 stated at package granularity.
Common Reuse Principle (CRP)
What Martin Said:
"The classes in a package are reused together. If you reuse one of the classes in a package, you reuse them all."
— Robert C. Martin, Agile Software Development (2003)
The Problem with CRP: CRP can conflict with CCP. CCP says "group things that change together"; CRP says "group things that are used together." These aren't always the same.
How IVP addresses this: Both principles are approximations of IVP-2. Classes that change together (same change driver) should be packaged together. Usage patterns are a heuristic for shared change drivers, but causal analysis is more precise.
Relationship: CRP emerges from IVP based on usage patterns.
Reuse/Release Equivalence Principle (REP)
What Martin Said:
"The granule of reuse is the granule of release."
— Robert C. Martin, Agile Software Development (2003)
The Problem with REP: REP is circular without additional criteria—what should be released together? It tells you the granule should match but not how to determine that granule.
How IVP addresses this: Elements sharing a change driver share a release lifecycle. If pricing rules and inventory rules change independently, they should release independently.
Relationship: REP is IVP applied to release management.
Acyclic Dependencies Principle (ADP)
What Martin Said:
"Allow no cycles in the package dependency graph."
— Robert C. Martin, Agile Software Development (2003)
The Problem with ADP: ADP is a structural rule but doesn't explain why cycles are problematic. Breaking cycles sometimes requires introducing abstractions that add complexity.
How IVP addresses this: Cycles are problematic because they force co-variation. Packages in a cycle must deploy/test together even if they have independent change drivers. Breaking the cycle isolates independent variation—but only do it if the packages genuinely vary independently.
Relationship: ADP is IVP-1 applied to prevent false variation coupling.
Stable Dependencies Principle (SDP)
What Martin Said:
"Depend in the direction of stability."
— Robert C. Martin, Agile Software Development (2003)
The Problem with SDP: "Stability" is defined circularly in terms of dependencies. A package is stable if many things depend on it. But should they depend on it? SDP assumes the dependency structure is correct.
How IVP addresses this: Stability should reflect change driver frequency. Packages with slow-changing drivers (core domain concepts) should be stable; packages with fast-changing drivers (UI, integrations) should be unstable. Dependencies should flow from fast-changing to slow-changing.
Relationship: SDP is IVP-1 applied to stability management.
Stable Abstractions Principle (SAP)
What Martin Said:
"A package should be as abstract as it is stable."
— Robert C. Martin, Agile Software Development (2003)
The Problem with SAP: SAP conflates two different things: stability (resistance to change) and abstraction (generality). A package can be stable because it's mature and rarely needs changes—without being abstract. SAP assumes abstraction is the only path to stability.
How IVP addresses this: Abstractions are stable because they unify elements with different specific change drivers under a common interface. The abstraction changes only when the concept changes, not when specific implementations change. But concrete packages can also be stable if they embody slowly-changing domain knowledge.
Relationship: SAP ensures stability through IVP-compliant abstraction.
GRASP Principles
Craig Larman introduced GRASP (General Responsibility Assignment Software Patterns) in Applying UML and Patterns (1997). These nine principles guide responsibility assignment in OO design.
Relevant Change Drivers
GRASP principles assign responsibilities based on knowledge ownership:
-
γ_knowledge— the class whose driver governs the knowledge owns the responsibility -
γ_uivs.γ_use_case— Controller mediates between independent drivers -
γ_domainvs.γ_technical— Pure Fabrication isolates technical variation
Information Expert
The Principle: Assign responsibility to the class with the necessary information to fulfill it.
The Problem with Information Expert: "Necessary information" is ambiguous. A class might have the data but lack the right to change it. Information location doesn't always indicate responsibility ownership. Multiple classes may have access to the same information—which is the "expert"?
How IVP addresses this: The "expert" is the class whose change driver governs that knowledge. If pricing logic changes for pricing reasons, the class owning pricing knowledge should implement pricing behavior—even if other classes have access to the data.
Relationship to IVP: Information Expert is IVP-2 for responsibility assignment. The expert is determined by which class's change driver governs the knowledge in question.
Creator
The Principle: Assign class B the responsibility to create instances of class A if B aggregates, contains, or closely uses A.
The Problem with Creator: The heuristics (aggregates, contains, closely uses) don't always align. A class might aggregate A but not be the right creator if instantiation logic varies independently from aggregation logic.
How IVP addresses this: Creation responsibility follows change drivers. If A's instantiation requirements change for the same reasons as B's behavior, B should create A. If instantiation varies independently (e.g., different creation strategies), extract creation to a factory.
Relationship to IVP: Creator is IVP-2 applied to object instantiation. Co-locate creation with usage when they share a change driver; separate when they don't.
Controller
The Principle: Assign responsibility for handling system events to a non-UI class representing the overall system or use case.
The Problem with Controller: Where should controller logic live? Bloated controllers ("god classes") violate SRP. Too many small controllers fragment use case logic. The principle provides no sizing criterion.
How IVP addresses this: Controllers isolate UI variation from use case variation. The UI changes for presentation reasons; use cases change for business reasons. These are independent change drivers—IVP-1 mandates separation. The controller is the boundary. Size controllers by change driver scope, not arbitrary rules.
Relationship to IVP: Controller is IVP-1 applied to UI/domain separation. The controller exists because UI and domain have independent change drivers.
Low Coupling / High Cohesion
The Principles: Minimize dependencies between classes; ensure each class has focused purpose.
The Problem with Low Coupling / High Cohesion: These are goals, not methods. "Minimize" and "focused" are vague. How low is low enough? How focused is focused enough? Without criteria, these become subjective judgments.
How IVP addresses this: These are the goals IVP achieves. Low coupling = not coupling independent change drivers. High cohesion = grouping elements with the same change driver. IVP explains why these goals matter and how to achieve them with precise criteria.
Relationship to IVP: Low Coupling is the goal of IVP-1; High Cohesion is the goal of IVP-2. IVP provides the operational definition of both.
Polymorphism
The Principle: Use polymorphic operations instead of explicit type checking.
The Problem with Polymorphism: When is polymorphism warranted? Adding interfaces for single implementations adds ceremony. The principle doesn't distinguish necessary polymorphism from over-engineering.
How IVP addresses this: Type checking couples client code to each variant's change driver. Polymorphism encapsulates each variant's variation, so the client varies only for its own concerns. Apply polymorphism when variants have independent change drivers from the client.
Relationship to IVP: Polymorphism is IVP-1 applied via type abstraction. It isolates independent variation behind a common interface.
Indirection
The Principle: Assign responsibility to an intermediate object to mediate between components.
The Problem with Indirection: Every indirection adds complexity. When is the indirection worth it? "All problems can be solved by another level of indirection"—but this creates its own problems.
How IVP addresses this: Indirection creates a boundary between independent change drivers. The intermediate object absorbs variation from both sides. But indirection without independent variation is unnecessary complexity—apply only when genuine independence exists.
Relationship to IVP: Indirection is IVP-1 implemented via intermediary. Warranted only when the mediated components have independent change drivers.
Protected Variations
The Principle: Wrap instability points with interfaces; use polymorphism for implementations.
The Problem with Protected Variations: What is an "instability point"? How do you identify them? The principle assumes you already know what's unstable.
How IVP addresses this: "Instability points" are locations where independent change drivers meet. Protected Variations is essentially IVP-1 stated as a pattern: isolate elements that vary independently behind stable interfaces. Change driver analysis identifies the instability points.
Relationship to IVP: Protected Variations is essentially IVP-1 stated as a pattern. IVP provides the criterion for identifying instability points.
Pure Fabrication
The Principle: Create classes that don't represent domain concepts to achieve low coupling and high cohesion.
The Problem with Pure Fabrication: When is fabrication justified? Creating non-domain classes can obscure domain logic. The principle provides no criterion for when fabrication helps versus hurts.
How IVP addresses this: Sometimes change driver boundaries don't align with domain concepts. A "service" class might exist purely to isolate technical variation (database access, external APIs) from domain variation. Pure Fabrication acknowledges that IVP-compliant structure sometimes requires non-domain classes—but only when change drivers demand it.
Relationship to IVP: Pure Fabrication supports IVP compliance when domain boundaries don't match change driver boundaries.
Functional Programming Principles
FP principles take a different approach to the IVP problem: rather than organizing variation, they eliminate sources of variation. This is complementary to IVP's organizational approach.
Relevant Change Drivers
FP principles often eliminate change drivers rather than organizing them:
-
γ_mutation— eliminated by immutability -
γ_side_effect— eliminated by pure functions -
γ_structurevs.γ_behavior— separated by higher-order functions -
γ_whatvs.γ_how— separated by declarative style
Immutability
The Principle: Once created, data cannot be modified. Changes produce new values rather than mutating existing ones.
The Problem with Mutability: Mutable state creates hidden variation—values can change unexpectedly, coupling code to temporal ordering and making reasoning difficult.
How IVP addresses this: Immutability eliminates a category of change drivers. Each piece of mutable state is a potential source of variation. Immutability removes these sources entirely—no variation means no independent variation to separate.
Relationship to IVP: Immutability achieves IVP compliance by elimination rather than organization. Fewer change drivers means fewer independence relationships to manage.
Pure Functions
The Principle: Functions that have no side effects and return the same output for the same input.
The Problem with Impurity: Impure functions have hidden dependencies—they read or write external state, coupling them to concerns outside their explicit interface.
How IVP addresses this: Pure functions achieve maximal cohesion by construction. A pure function:
- Has no side effects (no accidental coupling to external state)
- Depends only on its inputs (no hidden dependencies)
Pure functions naturally satisfy IVP-1: they can't accidentally couple to independent concerns because they have no access to them.
Relationship to IVP: Pure functions are IVP-1 compliant by construction. The function boundary perfectly isolates its change driver.
Referential Transparency
The Principle: An expression can be replaced with its value without changing program behavior.
The Problem without Referential Transparency: Refactoring is dangerous—you can't safely move or reorganize code because hidden dependencies might break.
How IVP addresses this: Referential transparency enables confident refactoring. If an expression can be replaced with its value, you can reorganize code without fear of breaking hidden dependencies. This supports IVP refactoring: separating concerns becomes safe when there are no hidden couplings.
Relationship to IVP: Referential transparency enables IVP-compliant reorganization. It guarantees that structural changes don't introduce accidental coupling.
Function Composition
The Principle: Build complex functions by combining simpler ones: (f ∘ g)(x) = f(g(x)).
The Problem with Monolithic Functions: Large functions mix multiple concerns, making them hard to change and test independently.
How IVP addresses this: Composition respects change driver boundaries. In f ∘ g, function f varies for its concerns, g varies for its concerns. Composition maintains independence: changing g's implementation doesn't affect f (as long as types match).
Relationship to IVP: Function composition is IVP-1 at the function level. Each function encapsulates its own change drivers, and composition preserves that isolation.
Higher-Order Functions
The Principle: Functions that take functions as arguments or return functions as results, enabling abstraction over behavior.
The Problem without Higher-Order Functions: Behavioral variation requires code duplication or complex conditional logic, coupling iteration structure to transformation logic.
How IVP addresses this: Higher-order functions isolate behavioral variation from structural variation. A higher-order function like map separates:
- The iteration mechanism (structural concern) — changes for algorithm reasons
- The transformation logic (behavioral concern) — changes for business reasons
These are independent change drivers. map encapsulates iteration so that transformation logic can vary independently.
Relationship to IVP: Higher-order functions are IVP-1 applied to behavioral abstraction. They isolate structure from behavior when these have independent change drivers.
Declarative Over Imperative
The Principle: Describe what to compute, not how to compute it. Let the runtime determine execution strategy.
The Problem with Imperative: Imperative code couples what you want with how it's achieved. Changes to performance strategy require modifying business logic.
How IVP addresses this: Declarative code separates intent from mechanism.
- Intent changes for business reasons (
γ_business) - Mechanism changes for performance/runtime reasons (
γ_runtime)
These are independent change drivers. Declarative code expresses intent; the runtime handles mechanism. Query optimization in SQL, lazy evaluation in Haskell—these are IVP-1 in action.
Relationship to IVP: Declarative style is IVP-1 applied to intent/mechanism separation. Business logic and execution strategy have independent change drivers.
Algebraic Data Types (ADTs)
The Principle: Model data as sum types (alternatives) and product types (combinations), making illegal states unrepresentable.
The Problem without ADTs: Variation is implicit, scattered across conditionals. Missing cases cause runtime errors. Related variants aren't grouped.
How IVP addresses this: ADTs make variation explicit in types. A sum type like Result<T, E> = Ok(T) | Err(E) explicitly models two variation cases. Pattern matching forces handling both. This prevents accidental coupling—you can't ignore a variant.
Relationship to IVP: ADTs achieve IVP-2 by grouping related variations in a single type, while pattern matching ensures each case is handled by code that varies for that case's reasons.
Currying and Partial Application
The Principle: Transform multi-argument functions into chains of single-argument functions. Apply some arguments now, the rest later.
The Problem without Currying: Configuration and execution are entangled. Every call must provide all arguments, even when some are fixed for a context.
How IVP addresses this: Currying enables staged configuration.
configure :: Config -> Request -> Response
Partially applying config creates a specialized handler. Configuration varies for deployment reasons (γ_ops); request handling varies for feature reasons (γ_feature). Currying separates these change drivers across time—configure once, use many times.
Relationship to IVP: Currying is IVP-1 applied across time. It separates configuration (early-bound) from execution (late-bound) when these have independent change drivers.
Lazy Evaluation
The Principle: Defer computation until results are needed. Compute only what's actually used.
The Problem with Eager Evaluation: Definition and execution are coupled. You can't define infinite structures or separate "what to compute" from "when to compute it."
How IVP addresses this: Laziness decouples definition from execution. You define what a value is (its structure) separately from when it's computed (execution strategy). Definition varies for business reasons; execution timing varies for performance reasons.
Relationship to IVP: Lazy evaluation is IVP-1 applied to computation timing. Definition and execution have independent change drivers that laziness separates.
Summary: What IVP Brings
Classic Principles (1960s–1990s)
| Principle | IVP Relationship | What IVP Adds |
|---|---|---|
| Separation of Concerns | IVP operationalizes | Objective criterion for "what's a concern" |
| Information Hiding | Complementary | IVP provides structure; IH provides economic criterion |
| DRY | Context-dependent | Criterion for when to deduplicate vs. keep separate |
| KISS | IVP operationalizes | Precise definition of "unnecessary complexity" |
| Law of Demeter | IVP-1 at method scope | Explains why train wrecks are problematic |
| PoLS | Orthogonal | Different dimension (understandability vs. evolvability) |
| Conway's Law | IVP judges | Criterion for when to embrace vs. resist mirroring |
| Tell, Don't Ask | IVP refines | Co-locate when behavior shares change driver with data |
| CQS | Complementary | CQS ensures predictability; IVP ensures evolvability |
XP and Agile Principles (1990s–2000s)
| Principle | IVP Relationship | What IVP Adds |
|---|---|---|
| YAGNI | IVP operationalizes | Build abstractions when actual change drivers appear |
| Passes Tests | Orthogonal | Correctness, not evolvability |
| Reveals Intention | Orthogonal | Understandability, not evolvability |
| No Duplication | IVP-2 criterion | Same change driver = same knowledge |
| Fewest Elements | IVP defines necessity | One element per change driver |
| OAOO | IVP-2 criterion | Knowledge identity = change driver identity |
| Technical Excellence | IVP operationalizes | Excellence = IVP-compliant structure |
| Simplicity | IVP operationalizes | Simplicity = no accidental coupling |
| Emergent Design | IVP operationalizes | Teams discover change drivers; IVP guides structuring |
Systems-of-Systems Principles (1990s–2000s)
| Principle | IVP Relationship | What IVP Adds |
|---|---|---|
| Stable Intermediate Forms | IVP for evolution | Each intermediate state must isolate constituent change drivers |
| Policy Triage | IVP-1 via policy | Policy boundaries when structural control unavailable |
| Leverage Interfaces | DIP/IVP-1 at SoS scale | Standards isolate constituent change drivers |
| Ensure Cooperation | Organizational change drivers | Technical + organizational factors both matter |
Cloud-Native Principles (2010s)
| Factor | IVP Relationship | What IVP Adds |
|---|---|---|
| Config | IVP-1 for environment | Separates γ_app from γ_environment
|
| Backing Services | IVP-1 for providers | Separates γ_app from γ_provider
|
| Build/Release/Run | IVP-1 for lifecycle | Separates γ_build from γ_run
|
| Dev/Prod Parity | IVP alignment | Minimizes accidental γ_dev vs. γ_prod divergence |
| Processes | IVP-1 via statelessness | Isolates instances from each other |
GoF Principles
| Principle | IVP Relationship | What IVP Adds |
|---|---|---|
| Program to Interface | IVP-1 at type level | Apply when implementation might vary independently |
| Composition over Inheritance | IVP-1 for flexibility | Use inheritance only when parent/child share change driver |
SOLID Principles
| Principle | IVP Relationship | What IVP Adds |
|---|---|---|
| SRP | IVP at class level | Precise definition of "reason to change" |
| OCP | IVP-1 for stable/variant separation | Criterion for when extension points are needed |
| LSP | IVP-2 behavioral constraint | Explains why contracts matter (prevents false unification) |
| ISP | IVP-1 at interface level | Criterion for segregation boundaries |
| DIP | IVP-1 at layer level | Criterion for when abstraction is necessary |
Package Principles
| Principle | IVP Relationship | What IVP Adds |
|---|---|---|
| CCP | Literally IVP-2 | Defines "same kind of change" as same change driver |
| CRP | IVP-2 via usage | Usage patterns as heuristic for shared change drivers |
| REP | IVP for releases | Change driver determines release granularity |
| ADP | IVP-1 for cycles | Explains why cycles are problematic (forced co-variation) |
| SDP | IVP-1 for stability | Stability = slow-changing drivers; dependencies flow toward stability |
| SAP | IVP via abstraction | Abstractions stable because they unify diverse implementations |
GRASP Principles
| Principle | IVP Relationship | What IVP Adds |
|---|---|---|
| Information Expert | IVP-2 for responsibility | Expert = class whose change driver governs the knowledge |
| Creator | IVP-2 for instantiation | Creation follows change driver alignment |
| Controller | IVP-1 for UI separation | Isolates UI variation from use case variation |
| Low Coupling | Goal of IVP-1 | Don't couple independent change drivers |
| High Cohesion | Goal of IVP-2 | Group elements with same change driver |
| Polymorphism | IVP-1 via interfaces | Encapsulates each variant's change driver |
| Indirection | IVP-1 boundary | Creates boundary between independent change drivers |
| Protected Variations | Essentially IVP-1 | Isolate independent variation behind stable interfaces |
| Pure Fabrication | IVP-compliant structure | Non-domain classes for change driver isolation |
Functional Programming Principles
| Principle | IVP Relationship | What IVP Adds |
|---|---|---|
| Immutability | Eliminates variation | Removes a category of change drivers entirely |
| Pure Functions | Maximal cohesion by construction | No accidental coupling possible |
| Referential Transparency | Enables IVP refactoring | Safe to reorganize when no hidden dependencies |
| Function Composition | IVP-1 at function level | Preserves change driver isolation across composition |
| Higher-Order Functions | IVP-1 for behavior | Isolates behavioral variation from structural variation |
| Declarative over Imperative | IVP-1 for intent/mechanism | Separates business intent from runtime mechanism |
| Algebraic Data Types | IVP-2 for variants | Makes variation explicit; groups related cases |
| Currying | IVP-1 across time | Separates configuration from execution |
| Lazy Evaluation | IVP-1 for execution | Decouples definition from execution timing |
Why Existing Principles Still Matter
I want to be clear about something: this analysis should not be read as suggesting that established principles are obsolete or that practitioners should abandon them in favor of IVP alone. Quite the opposite.
IVP Requires Domain Expertise
IVP's generality—separate elements that vary for different causes; unify elements that vary for the same cause—is both its strength and its challenge. Applying IVP effectively requires the ability to identify change drivers, and identifying change drivers requires domain expertise.
Consider a developer new to a codebase. They may not yet understand:
- Who the stakeholders are and what each cares about
- Which external systems the application integrates with
- What regulations govern different parts of the business
- Which parts of the system are stable versus volatile
- The historical patterns of change revealed in version control
Without this context, "identify the change drivers" is not immediately actionable advice. The developer knows the principle but lacks the knowledge to apply it.
Established Principles as Actionable Heuristics
This is where the established principles prove their enduring value, despite presenting serious flaws in some cases. Each principle encapsulates domain-independent patterns that experienced practitioners have observed across many contexts:
SRP: "Does this class have multiple reasons to change?" Even without knowing the specific change drivers, asking this question prompts useful reflection.
DRY: "Am I duplicating knowledge?" This triggers examination of whether code fragments represent the same underlying concept.
SOLID: Each principle offers a specific lens for examining code structure, with concrete structural indicators (interface size, dependency direction, substitutability).
Law of Demeter: "Am I reaching through too many objects?" This is immediately observable without deep domain knowledge.
12-Factor: The factors provide a checklist applicable to any cloud-native application, regardless of business domain.
These principles remain immediately actionable because they operate on structural properties visible in the code itself, not on causal relationships that require domain understanding.
IVP + Established Principles: Complementary Tools
The relationship between IVP and established principles is not replacement but complementarity:
| Situation | Best Approach |
|---|---|
| Experienced in the domain, understand change drivers | Use IVP directly for principled decisions |
| New to domain, learning the codebase | Use established principles as guardrails while building domain understanding |
| Principles conflict or seem to recommend different actions | Use IVP to resolve the conflict by analyzing change drivers |
| Explaining a decision to others | Use IVP to provide the why, established principles for the what |
| Teaching junior developers | Start with concrete principles, introduce IVP as the unifying explanation |
Principles Provide Context IVP Lacks
Each established principle carries contextual information that IVP's generality cannot provide:
- SRP focuses attention on the class level
- ISP focuses attention on interface design
- DIP focuses attention on layer boundaries
- Law of Demeter focuses attention on method call chains
- 12-Factor focuses attention on deployment concerns
When combined with IVP's unifying insight, these principles become more powerful, not less. You understand not just what to do but why it works—and crucially, when to deviate because the change driver analysis suggests a different approach.
The Learning Journey
For practitioners at different experience levels:
Beginners: Learn the established principles. They provide concrete, actionable guidance that improves code quality immediately. Don't worry yet about the deeper unification.
Intermediate: Start noticing when principles conflict or when applying them "correctly" still produces poor results. This is the signal to look deeper—to ask why these principles exist.
Advanced: Use IVP as the foundational lens. Recognize established principles as constrained instantiations of IVP. Apply IVP directly when you understand the change drivers; fall back to established principles as reliable heuristics when domain knowledge is incomplete.
Expert: Teach both. Use established principles to make ideas concrete and accessible. Use IVP to explain the underlying unity and to guide decisions in novel situations where no established principle directly applies.
When No Existing Principle Applies
Sometimes you face a design decision where none of the established principles seem directly relevant:
- A novel architectural pattern with no precedent in the literature
- A domain-specific structure that doesn't map to standard OO or FP patterns
- A cross-cutting concern that spans multiple traditional boundaries
- An integration point between systems with incompatible assumptions
In these situations, IVP is always applicable. The question "What causes this to change, and are those causes independent?" applies universally—to code, configuration, infrastructure, data schemas, team structures, and any other aspect of software systems.
The established principles are IVP applied to common, recurring situations. When you encounter an uncommon situation, you can derive the appropriate guidance directly from IVP by analyzing the change drivers involved.
The Principles Remain Valid
Nothing in this article invalidates any of the principles discussed. SRP is still correct: a class should have only one reason to change. IVP simply explains what a "reason to change" is (a change driver) and provides the criterion for identifying when you've achieved it.
The principles represent five decades of accumulated wisdom about software structure. IVP doesn't replace this wisdom—it explains it, unifies it, and extends it to situations where the established principles don't directly apply.
Use IVP to understand why the principles work. Use the principles to make IVP actionable in your daily work. And when no principle seems to fit, use IVP directly.
Practical Takeaways
One principle to rule them all: Instead of memorizing 15+ independent principles, learn IVP and derive specific guidance for any situation through change driver analysis.
Empirical, not predictive: Analyze your version control history. What actually changes together? What changes independently? Let causal reality guide structure.
Question before abstracting: Before adding interfaces, inheritance, or shared utilities, verify that you're dealing with genuinely independent or dependent variation. Abstraction without independent variation is premature.
Cohesion primacy: Focus on cohesion, not coupling. Accidental coupling manifests as reduced cohesion (foreign change drivers). Maximize cohesion and coupling takes care of itself.
Architecture is knowledge work: You're not just managing dependencies. You're organizing domain knowledge. Each module should completely and purely embody one domain's knowledge.
References
IVP
- Loth, Y. (2025). "The Independent Variation Principle - A Unifying Meta-Principle for Software Architecture"
Classic Principles
- Beck, K. (2000). Extreme Programming Explained: Embrace Change. Addison-Wesley. (Four Rules of Simple Design, YAGNI, OAOO)
- Conway, M.E. (1968). "How Do Committees Invent?" Datamation, 14(4), 28–31.
- Dijkstra, E.W. (1974). "On the Role of Scientific Thought." EWD447.
- Hunt, A. & Thomas, D. (1999). The Pragmatic Programmer: From Journeyman to Master. Addison-Wesley.
- Lieberherr, K., Holland, I., & Riel, A. (1988). "Object-Oriented Programming: An Objective Sense of Style." OOPSLA '88 Proceedings, 323–334.
- Meyer, B. (1988). Object-Oriented Software Construction. Prentice Hall. (CQS origin)
- Parnas, D.L. (1972). "On the Criteria To Be Used in Decomposing Systems into Modules." Communications of the ACM, 15(12), 1053–1058.
Agile and Cloud-Native
- Beck, K. et al. (2001). "Manifesto for Agile Software Development". (Agile Manifesto)
- Beck, K. et al. (2001). "Principles behind the Agile Manifesto". (12 Principles)
- Wiggins, A. (2011). "The Twelve-Factor App". Heroku.
Systems-of-Systems
- INCOSE (2022). "Systems Engineering Principles". International Council on Systems Engineering.
- Maier, M.W. (1998). "Architecting Principles for Systems-of-Systems." Systems Engineering, 1(4), 267–284.
- Maier, M.W. & Rechtin, E. (2009). The Art of Systems Architecting. 3rd ed. CRC Press.
OO Design Patterns and Principles
- Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. (GoF)
- Larman, C. (2004). Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design. 3rd ed. Prentice Hall. (GRASP)
SOLID Principles
- Liskov, B. & Wing, J. (1994). "A Behavioral Notion of Subtyping." ACM Transactions on Programming Languages and Systems, 16(6), 1811–1841.
- Martin, R.C. (1996). "The Open-Closed Principle." C++ Report.
- Martin, R.C. (1996). "The Dependency Inversion Principle." C++ Report.
- Martin, R.C. (1996). "The Interface Segregation Principle." C++ Report.
- Martin, R.C. (2003). Agile Software Development: Principles, Patterns, and Practices. Prentice Hall.
- Martin, R.C. (2017). Clean Architecture: A Craftsman's Guide to Software Structure and Design. Prentice Hall.
Critiques and Alternatives
- Dodds, K.C. (2019). "AHA Programming" — Avoid Hasty Abstractions.
- North, D. (2022). "CUPID—for joyful coding" — Properties over principles.
- Swizec, T. (2020). "DRY—the common source of bad abstractions".
- Tedinski, T. (2019). "Deconstructing SOLID design principles".
IVP Paper: https://zenodo.org/records/18024111
Top comments (0)