SOLID Heuristics Reveal Incomplete Domain Knowledge — Nothing More
SOLID Heuristics Reveal Incomplete Domain Knowledge — Nothing More
SOLID 启发式方法揭示了领域知识的不完整性——仅此而已
SOLID(单一职责、开闭原则、里氏替换、接口隔离、依赖倒置)作为五个独立的软件设计原则,已经被传授了三十年 [Martin 1996a, 1996b, 2000, 2003]。每一位软件工程师都学习过它们,每一次代码审查都会引用它们。本文认为,SOLID 中的每一项启发式方法在形式意义上都不是设计原则,而是针对领域知识如何不完整或表达错误的一种诊断模式。这些启发式方法之所以存在,仅仅是因为我们在实践中没有完整地记录因果领域知识。如果我们能够做到这一点,正确的架构设计就会自动产生,无需任何启发式检查。
How each case is presented
各案例的呈现方式
For each heuristic that addresses change management (SRP, OCP, ISP, DIP), the analysis follows a three-step method: 对于每一项涉及变更管理的启发式方法(SRP、OCP、ISP、DIP),分析过程遵循以下三个步骤:
-
Knowledge with the gap. A domain statement as it is typically written — incomplete or compound — producing flawed code. The SOLID heuristic correctly flags this code. But the heuristic identifies the symptom, not the cause.
-
存在缺陷的知识。 领域陈述通常以不完整或复合的形式书写,从而产生有缺陷的代码。SOLID 启发式方法能正确标记出这些代码,但它识别的是症状,而非根本原因。
-
Knowledge without the gap. The same domain knowledge expressed correctly — decomposed into irreducible sub-statements, each governed by a distinct change driver. This is the knowledge correction. It is not a code refactoring; it is an epistemic act: recognizing what can independently cause each element to change.
-
无缺陷的知识。 以正确方式表达的相同领域知识——将其分解为不可约的子陈述,每个子陈述由不同的变更驱动因素(change driver)控制。这是一种知识修正。它不是代码重构,而是一种认知行为:识别出什么因素可以独立导致每个元素发生变更。
-
IVP application. The corrected knowledge yields driver assignments Γ. IVP-2 decomposes reducible composites. IVP-3 and IVP-4 produce the unique module partition. The structure the heuristic prescribed emerges automatically. The heuristic itself was never invoked — it was only needed because the knowledge had a gap.
-
IVP 应用。 修正后的知识会产生驱动因素分配 Γ。IVP-2 分解可约的复合体。IVP-3 和 IVP-4 产生唯一的模块划分。启发式方法所规定的结构会自动浮现。启发式方法本身从未被调用——它之所以被需要,仅仅是因为知识存在缺陷。
LSP is treated separately: it is a hybrid. IVP still determines which types share a driver and therefore belong in the same module. When the structural placement is wrong — types with different invariants forced into a hierarchy — correcting Γ resolves the violation like the others. When the structural placement is correct but the behavioral contract is broken, Bertrand Meyer’s Design by Contract — a type-theoretic discipline — takes over. LSP(里氏替换原则)被单独处理:它是一个混合体。IVP 仍然决定哪些类型共享同一个驱动因素,从而属于同一个模块。当结构布局错误时(即具有不同不变式的类型被强行放入同一个层级),修正 Γ 可以像处理其他原则一样解决违规问题。当结构布局正确但行为契约被破坏时,Bertrand Meyer 的“契约式设计”(一种类型理论学科)将接管后续工作。
The argument rests on the Independent Variation Principle (IVP), a formal theory of software modularization. I state it first. 该论点基于独立变异原则(Independent Variation Principle, IVP),这是一种软件模块化的形式理论。我先对其进行定义。
The Independent Variation Principle
独立变异原则 (IVP)
IVP defines a software system as a six-tuple S = (F, κ_F, E, K, C, Γ) where: IVP 将软件系统定义为一个六元组 S = (F, κ_F, E, K, C, Γ),其中:
- F is the set of functional purposes the system must fulfill
- F 是系统必须满足的功能目的集合
- κ_F is the causal domain knowledge the system must embody to fulfill F
- κ_F 是系统为实现 F 所必须体现的因果领域知识
- E is the set of software elements — classes, functions, modules
- E 是软件元素的集合——类、函数、模块
- K is the universe of all possible domain knowledge
- K 是所有可能领域知识的集合
- C is the set of change drivers — forces that can causally require elements to be modified (regulations, contracts, interface specifications, stakeholder commitments, hardware evolution)
- C 是变更驱动因素的集合——即能够因果性地要求元素进行修改的力量(如法规、契约、接口规范、利益相关者承诺、硬件演进)
- Γ : E → P(C) is the driver assignment function, mapping each element to the set of change drivers that can cause it to require modification
- Γ : E → P(C) 是驱动因素分配函数,将每个元素映射到可能导致其需要修改的变更驱动因素集合
The driver assignment Γ is the central object. Γ(e) answers one question: what can cause this element to change? The answer is not a design preference — it is a fact about the system’s causal reality. Two engineers who correctly analyze the same system must arrive at the same Γ. If they disagree, at least one analysis is incomplete. 驱动因素分配 Γ 是核心对象。Γ(e) 回答了一个问题:什么会导致该元素发生变化?这个答案不是设计偏好,而是关于系统因果现实的事实。两位正确分析同一系统的工程师必须得出相同的 Γ。如果他们意见不一致,说明至少有一方的分析是不完整的。
The four IVP directives are: IVP 的四项指令如下:
-
IVP-1 — Element Admissibility. Every element must have at least one change driver: |Γ(e)| ≥ 1 for all e ∈ E. An element with no driver serves no purpose and should be removed.
-
IVP-1 — 元素准入性。 每个元素必须至少有一个变更驱动因素:对于所有 e ∈ E,|Γ(e)| ≥ 1。没有驱动因素的元素没有任何用途,应当被移除。
-
IVP-2 — Change Driver Assignment. Every element is either pure (|Γ(e)| = 1) or irreducibly composite (|Γ(e)| > 1 with no decomposition possible without losing function). A reducible composite — an element that could be split to reduce its driver set — is a design defect. A reducible element is evidence that domain knowledge was over-specified, conflated, or incompletely analyzed.
-
IVP-2 — 变更驱动因素分配。 每个元素要么是纯粹的(|Γ(e)| = 1),要么是不可约的复合体(|Γ(e)| > 1,且无法在不丢失功能的情况下进行分解)。可约的复合体——即可以通过拆分来减少其驱动因素集合的元素——是一种设计缺陷。可约元素证明了领域知识被过度指定、混淆或分析不完整。
-
IVP-3 — Unit Purity (Separation). Elements with different driver assignments must not occupy the same module: ∀ M ∈ M, ∀ e, e’ ∈ M : Γ(e) = Γ(e’).
-
IVP-3 — 单元纯度(分离)。 具有不同驱动因素分配的元素不得占用同一个模块:∀ M ∈ M, ∀ e, e’ ∈ M : Γ(e) = Γ(e’)。
-
IVP-4 — Unit Completeness (Unification). Elements with the same driver assignment must occupy the same module: ∀ e, e’ : Γ(e) = Γ(e’) ⇒ ∃ M ∈ M : {e, e’} ⊆ M.
-
IVP-4 — 单元完整性(统一)。 具有相同驱动因素分配的元素必须占用同一个模块:∀ e, e’ : Γ(e) = Γ(e’) ⇒ ∃ M ∈ M : {e, e’} ⊆ M。
IVP-3 and IVP-4 together form a biconditional: two elements belong in the same module if and only if they share the same driver assignment. The modularization is precisely the partition of E induced by e₁ ∼ e₂ ⇔ Γ(e₁) = Γ(e₂). Four directives. No heuristics. No weighing of trade-offs. The answer falls out of Γ. IVP-3 和 IVP-4 共同构成了一个双向条件:两个元素属于同一个模块,当且仅当它们共享相同的驱动因素分配。模块化正是由 e₁ ∼ e₂ ⇔ Γ(e₁) = Γ(e₂) 所诱导的 E 的划分。四项指令,没有启发式方法,没有权衡取舍,答案直接从 Γ 中得出。
SRP: Bundled Causal Concerns
SRP:捆绑的因果关注点
Knowledge with the gap 存在缺陷的知识
Domain statement: “The system calculates employee pay, formats pay statements, and emails them to employees.” This is a compound statement bundling three independent concerns. 领域陈述:“系统计算员工薪资,格式化薪资单,并将其通过电子邮件发送给员工。”这是一个捆绑了三个独立关注点的复合陈述。
The resulting code: 由此产生的代码:
class PayCalculator {
BigDecimal calculatePay(Employee e) { /* tax rules, benefits, overtime */ }
String formatPayStatement(Employee e) { /* PDF layout, company logo, line items */ }
void emailPayStatement(Employee e, String statement) { /* SMTP config, attachments */ }
}
SRP says: “A class should have only one reason to change” [Martin 2003]. PayCalculator has three: tax rules change, statement layout changes, email infrastructure changes. SRP flags this — correctly — as a defect. But SRP does not tell you why the defect exists. SRP 指出:“一个类应该只有一个引起它变化的原因” [Martin 2003]。PayCalculator 有三个原因:税收规则变更、报表布局变更、电子邮件基础设施变更。SRP 正确地将其标记为缺陷,但 SRP 并未告诉你为什么存在这个缺陷。
Knowledge without the gap 无缺陷的知识
Domain statement (corrected): “Pay calculation is governed by tax regulations and benefits contracts.” “Pay statement formatting is governed by document layout standards.” “Pay statement delivery is governed by the organization’s mail infrastructure.” 领域陈述(修正后):“薪资计算受税收法规和福利合同约束。”“薪资单格式化受文档布局标准约束。”“薪资单交付受组织邮件基础设施约束。”
Three irreducible statements, each governed by a distinct authority. The correction is not a code refactoring — it is a knowledge revision: the compound statement has been decomposed. 三个不可约的陈述,每个都由不同的权威机构管理。这种修正不是代码重构,而是知识修订:复合陈述已被分解。
IVP application IVP 应用
From the corrected knowledge, the driver assignments follow directly: 根据修正后的知识,驱动因素分配直接得出:
| Element | Γ (change drivers) |
|---|---|
| TaxCalculator | {γ_tax} |
| PayStatementFormatter | {γ_format} |
| PayStatementMailer | {γ_email} |
Each element is pure: |Γ(e)| = 1. IVP-2 is satisfied. The driver sets differ ({γ_tax… 每个元素都是纯粹的:|Γ(e)| = 1。IVP-2 得到满足。驱动因素集合各不相同({γ_tax…