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),分析过程遵循以下三个步骤:

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

  2. 存在缺陷的知识。 领域陈述通常以不完整或复合的形式书写,从而产生有缺陷的代码。SOLID 启发式方法能正确标记出这些代码,但它识别的是症状,而非根本原因。

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

  4. 无缺陷的知识。 以正确方式表达的相同领域知识——将其分解为不可约的子陈述,每个子陈述由不同的变更驱动因素(change driver)控制。这是一种知识修正。它不是代码重构,而是一种认知行为:识别出什么因素可以独立导致每个元素发生变更。

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

  6. 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…