|
| 1 | +# 对于《目前程序语言与软件工程研究中真正严重的缺陷是什么?》一文的解读 |
| 2 | + |
| 3 | +在我的前一篇文章[《目前程序语言与软件工程研究中真正严重的缺陷是什么?》](https://mp.weixin.qq.com/s/lJyGfNa3psbVxeSO1sji-A)中,我提出了一个核心观点:当代编程语言与软件工程的主流理论,把注意力几乎全部集中在“单个程序的静态快照”上,却缺乏一套以“变化本身”为一等公民、面向程序族长期演化的统一理论框架。为此,我引入了“广义可逆计算(GRC)”及其核心公式 `Y = F(X) ⊕ Δ`,试图把“生成”和“演化”统一在一条方程之中。 |
| 4 | + |
| 5 | +这篇补充说明的目的,是把原文中一些隐含但关键的思想明确拉到前台: |
| 6 | +- Δ 并不是对文本做 diff,而是相对于某个**结构空间**中的“位移/扰动”; |
| 7 | +- 构造与修改在形式上都是差量:全量构造可以视为“从空状态出发的一次大 Δ”; |
| 8 | +- `Y = F(X) ⊕ Δ` 的要求不仅是“生成 + 手工补充”,而是要求 F(X) 和 Δ 处在**同一结构坐标系**中,并且这条方程在应用层、DSL 层、元模型层、工具层以“分形”方式层层复用; |
| 9 | +- “可逆性”不仅意味着可以 undo 某次修改,还意味着可以在“构造逻辑空间”中重排、插入、回溯构造步骤,从而在演化中控制熵增; |
| 10 | +- GRC 不是要取代 DDD、MDA、SPL、DSL 工程,而是希望为它们提供一个共同的“数学母体”:用“结构空间 + Δ/⊕”来统一表达这些方法论中的生成、变体和定制逻辑。 |
| 11 | + |
| 12 | +通过对这些前提的澄清,我们可以更清晰地看到: |
| 13 | +GRC 想补的,不是一两个工程技巧的缺口,而是一整块“关于变化的科学”的理论空白——让差量 Δ、合并 ⊕、结构空间与可逆性,真正回到软件理论的中心位置。 |
| 14 | + |
| 15 | +## 一、原文在讲“差量(Δ)”时,其实暗含了一个“结构空间”的概念 |
| 16 | + |
| 17 | +原文一直在讲: |
| 18 | + |
| 19 | +- 差量 Δ; |
| 20 | +- 合并 ⊕; |
| 21 | +- 程序族与演化路径。 |
| 22 | + |
| 23 | +容易遗漏的是:这些概念不是悬空存在的,它们总是相对于某个“空间”而言。 |
| 24 | + |
| 25 | +### 1. 什么叫“结构空间”? |
| 26 | + |
| 27 | +原文在多个地方隐含地用了这个想法: |
| 28 | + |
| 29 | +> 一门语言/一个建模体系,并不只是一堆语法规则, |
| 30 | +> 而是定义了“它能生成的所有程序/模型”这一整片空间。 |
| 31 | +
|
| 32 | +你可以这么看: |
| 33 | + |
| 34 | +- 对 C 语言而言,“所有合法 C 程序的抽象语法树集合”,就是一个结构空间; |
| 35 | +- 对某个 DSL(比如工作流 DSL)而言,“所有合法的流程图结构”,也是一个结构空间。 |
| 36 | + |
| 37 | +原文提议: |
| 38 | +当我们谈 Δ 时,要明确**它是这片空间内部的一种“位移/微扰”**,而不是文件上的文本差异。 |
| 39 | + |
| 40 | +换句话说: |
| 41 | + |
| 42 | +- 文本 diff = 文件行的增删改; |
| 43 | +- 结构差量 = 在“可解释的程序结构空间”中操作某些节点/关系。 |
| 44 | + |
| 45 | +这就是为什么原文不停强调: |
| 46 | + |
| 47 | +- 不能只停留在 Git diff、JSON Patch 那种文本/语法层; |
| 48 | +- 必须在“程序/模型结构本身”的层级来定义 Δ。 |
| 49 | + |
| 50 | +我这里用“结构空间”这个词,是为了把这点显式化: |
| 51 | +**Δ 一定是相对于某个结构化的程序空间而言的,而不是随便对字节比一比。** |
| 52 | + |
| 53 | +--- |
| 54 | + |
| 55 | +## 二、“一切皆差量”背后,其实是想把“从零构造”和“修改”统一起来 |
| 56 | + |
| 57 | +原文有一句很容易被当成哲学感叹的话:“一切皆差量”。 |
| 58 | +这句话其实有一个非常具体的工程含义: |
| 59 | + |
| 60 | +### 1. 把全量构造看成“相对于空”的一次大 Δ |
| 61 | + |
| 62 | +在代数里,如果有一个“零元素 0”,就可以写: |
| 63 | + |
| 64 | +> A = 0 ⊕ A |
| 65 | +
|
| 66 | +原文借用了这个思路: |
| 67 | + |
| 68 | +- 一个系统从“无”到“有”的过程,可以看作对“空系统”应用了一个“大差量”; |
| 69 | +- 之后的每一次修改/定制,又是在原有状态上叠加更多 Δ。 |
| 70 | + |
| 71 | +这意味着: |
| 72 | + |
| 73 | +- **“生成出来的初始系统”** 和 |
| 74 | +- **“后面针对这个系统做的一次修改”** |
| 75 | + |
| 76 | +在形式上可以用同一种 Δ/⊕ 机制描述,只是基底不同: |
| 77 | + |
| 78 | +- 从 0 叠加 → “构造全量”; |
| 79 | +- 从 Base 叠加 → “在已有系统上演化”。 |
| 80 | + |
| 81 | +很多人初读时只注意到: |
| 82 | +“哦,Δ 是用来描述修改/定制的”,但容易忽略: |
| 83 | + |
| 84 | +> 原文其实是在说: |
| 85 | +> **构造 ≈ 一次大的 Δ, |
| 86 | +> 演化 ≈ 继续叠加 Δ。** |
| 87 | +
|
| 88 | +这才是“生成(F(X))和演化(⊕ Δ)可以统一进一条方程”的深层含义。 |
| 89 | + |
| 90 | +--- |
| 91 | + |
| 92 | +## 三、Y = F(X) ⊕ Δ 这条公式最关键的两个“强要求” |
| 93 | + |
| 94 | +对很多人来说,这个公式很直观: |
| 95 | + |
| 96 | +- 用 F(X) 表示“从模型 X 自动生成的部分”; |
| 97 | +- 用 Δ 表示“手工补充/修正的部分”。 |
| 98 | + |
| 99 | +但原文真正苛刻的地方不在这个分解,而在它对两件事情提出的“强要求”: |
| 100 | + |
| 101 | +### 1. 要求 Δ 和 F(X) 在同一个“坐标系”里 |
| 102 | + |
| 103 | +原文提出“坐标系”的比喻,很多人会理解为“领域模型就是一种坐标系”。 |
| 104 | +这里有一个隐含但非常重要的意思: |
| 105 | + |
| 106 | +> 不管是生成的 F(X),还是后续叠加的 Δ, |
| 107 | +> 都必须在同一个结构坐标系里表达。 |
| 108 | +
|
| 109 | +什么意思? |
| 110 | + |
| 111 | +- 如果 F(X) 生成的是某种结构化模型(例如一棵“页面组件树”); |
| 112 | +- 那 Δ 也应该是对这棵树中具体节点的“定点修改/扩展”,而不是在生成结果上随便写脚本“乱 patch”。 |
| 113 | + |
| 114 | +反例是常见的做法: |
| 115 | + |
| 116 | +- 模型生成一堆代码; |
| 117 | +- 手工改动直接写在代码上; |
| 118 | +- 改动不再以“模型上的差量”存在,而是变成对生成产物的不可追踪修改。 |
| 119 | + |
| 120 | +原文的态度是: |
| 121 | +**这样的修改其实已经逃出了原来的结构空间,Δ 失去了“结构坐标”意义。** |
| 122 | + |
| 123 | +想真正满足 Y = F(X) ⊕ Δ 的“统一精神”,就意味着: |
| 124 | + |
| 125 | +- Δ 必须在“生成物的结构空间”里有明确坐标(类似“在这个组件树的这个节点上增加这个子节点”); |
| 126 | +- 而不是“在这个 .java 文件第 123 行插入几行”。 |
| 127 | + |
| 128 | +这点在原文里没有公式化,但埋了很多铺垫,很多读者会下意识略过。 |
| 129 | + |
| 130 | +### 2. 要求这条公式能在多层级上重复使用(分形递归) |
| 131 | + |
| 132 | +原文不只是说“在应用层可以写出 Y = F(X) ⊕ Δ”,而是暗示: |
| 133 | + |
| 134 | +- 在模型层也可以:领域模型 = (基础元模型) 经过某个 F,再 ⊕ Δ; |
| 135 | +- 在 DSL 层也可以:某 DSL = 从更抽象元语言 F 出来,再 ⊕ Δ; |
| 136 | +- 在工具层也可以:生成器本身 = 某基础生成器 F₀,加上 Δ_generator 形成 F₁。 |
| 137 | + |
| 138 | +区别在于: |
| 139 | + |
| 140 | +- 很多工程实践只在某一两层用“生成+手改”的模式,局部使用类似思路; |
| 141 | +- 原文希望的是:**整个软件构造/演化链条,从元模型到代码/配置,都能在同一模式下描述。** |
| 142 | + |
| 143 | +这就是为什么它会用“分形”“结构空间”“路径积分”等物理/数学比喻: |
| 144 | +它想要的是**一条贯通多层的统一表达**,不只是说“在某处引入 Δ 比较方便”。 |
| 145 | + |
| 146 | +--- |
| 147 | + |
| 148 | +## 四、“可逆性”三层含义里,最容易被忽略的是“过程可逆性” |
| 149 | + |
| 150 | +原文没有系统分层定义“可逆性”,但隐含了三个层次: |
| 151 | + |
| 152 | +1. **代数可逆性** |
| 153 | + - 有 Δ 的逆元 −Δ,可以表达“撤销某个变化”,使得 Δ ⊕ (−Δ) 约等于“无变化”。 |
| 154 | + |
| 155 | +2. **变换可逆性** |
| 156 | + - 类似双向变换(BX): |
| 157 | + 从高层模型到低层表示有 F,从低层返回高层有 F⁻¹; |
| 158 | + 变换尽量信息不丢失。 |
| 159 | + |
| 160 | +3. **过程可逆性**(最容易一笔带过) |
| 161 | + - 构建流程通常被视为:先有库,再构建应用;先有 v1,再演化出 v2。 |
| 162 | + - 原文在讲“用未来的 Δ 修正历史产物”时,其实在重新理解这个时间顺序: |
| 163 | + - 你不一定要修改库源码; |
| 164 | + - 可以在“后来”的一个地方定义 Δ,让运行时或重构时“向后作用”于已存在的东西; |
| 165 | + - 在逻辑构造空间里,相当于把这步 Δ 插入到了更早的位置。 |
| 166 | + |
| 167 | +这第三层的意思是: |
| 168 | + |
| 169 | +> 我们平时把构造视为严格单向的时间线, |
| 170 | +> GRC 主张在“构造逻辑空间”里,这条线可以被重排、可逆、可插入。 |
| 171 | +
|
| 172 | +工程上对应的形态包括: |
| 173 | + |
| 174 | +- 对第三方库做“非侵入式定制/修复”,却还保留体系结构上的可追踪和可撤销; |
| 175 | +- 对已发布系统做“热修复”,其逻辑可被归入统一的 Δ/⊕ 模型中,而不是某种“特殊手段”。 |
| 176 | + |
| 177 | +很多人看到“可逆计算”容易想到的只是“能不能 undo”,而忽略了这一层“重新编排构造过程”的含义。 |
| 178 | + |
| 179 | +--- |
| 180 | + |
| 181 | +## 五、文章对现有理论的批评,其实是在“拉高度”,而不是“否定其价值” |
| 182 | + |
| 183 | +原文对类型论、形式方法、范畴论在 PL 中的用法提出了不少批评。初看会有一种“你是不是过于看轻这些领域”的感觉。但如果结合全文逻辑,可以这样更准确理解: |
| 184 | + |
| 185 | +1. 它不否认现有成果在“单程序静态性质”上的成功; |
| 186 | +2. 它指出的是:**在“程序族 + 演化”维度上,我们还没有类似类型论那样成熟的统一主干。** |
| 187 | +3. 它批评的重点不是“现在做的错了”,而是“我们到目前为止,重心选在了一个偏静态的切面上,而另一个维度还没有被当成‘中心任务’。” |
| 188 | + |
| 189 | +换句话说,文章想传达的并不是: |
| 190 | + |
| 191 | +> 你们研究类型/形式方法都错了,应该来搞 GRC。 |
| 192 | +
|
| 193 | +而更接近: |
| 194 | + |
| 195 | +> 静态安全这块基础已经很牢了, |
| 196 | +> 但“变化/演化”的基础理论还没立起来, |
| 197 | +> GRC 是一个尝试在这个空白处立新柱子的方向。 |
| 198 | +
|
| 199 | +这一点如果没有意识到,很容易误解为一种“旧山不如新山”的姿态。实际上,它希望的是**多一座“关于变化”的山**,而不是要把原有那座推倒。 |
| 200 | + |
| 201 | +--- |
| 202 | + |
| 203 | +## 六、原文和 DDD/MDA/SPL 的关系:GRC 不是来“取代”它们,而是要给它们一个统一的“数学母体” |
| 204 | + |
| 205 | +原文多次提到: |
| 206 | + |
| 207 | +- 模型驱动(MDA/MDD); |
| 208 | +- 软件产品线(SPL); |
| 209 | +- 领域驱动设计(DDD); |
| 210 | +- DSL 工程。 |
| 211 | + |
| 212 | +容易出现的误读是: |
| 213 | + |
| 214 | +- GRC 是一套“更好的 MDE/SPL/DDD”; |
| 215 | +- 旧的方法论不够好,现在要换用 GRC。 |
| 216 | + |
| 217 | +更准确的理解是: |
| 218 | + |
| 219 | +- MDA/MDD 主要关注从模型到实现的**一次性生成**; |
| 220 | +- SPL 关注的是产品族的**特性组合**; |
| 221 | +- DDD 关注的是如何在业务领域建立好的**语言与模型边界**; |
| 222 | +- GRC 想做的是:**把这些东西都放进同一个“结构空间 + Δ/⊕”的统一框架中来解释**。 |
| 223 | + |
| 224 | +举例: |
| 225 | + |
| 226 | +- MDA:传统是 `App = Transform(Model)`; |
| 227 | + GRC 视角下是 `App = F(Model) ⊕ Δ`,迁移了“生成后手改”的那块到 Δ 中。 |
| 228 | +- SPL:一般谈 feature 组合、变体; |
| 229 | + GRC 视角下是 `Variant = Base ⊕ Δ_feature ⊕ Δ_customer ⊕ …`,特性/客户定制都成为差量层。 |
| 230 | +- DDD:谈限界上下文、领域事件; |
| 231 | + GRC 视角下,限界上下文可以理解为不同的结构子空间,领域事件则是状态空间上的 Δ: |
| 232 | + |
| 233 | + `NewState = OldState ⊕ EventΔ` |
| 234 | + |
| 235 | +所以,更合适的比喻是: |
| 236 | + |
| 237 | +> GRC 希望当“母语言”, |
| 238 | +> 用同一套 Δ/⊕ 结构去表达 MDA/SPL/DDD/DSL 等已有方法论的构造和演化逻辑, |
| 239 | +> 而不是要发明一套与它们竞争的“新方法论”。 |
| 240 | +
|
| 241 | +## 七、如果读完原文还觉得模糊,可以用一个简化图景来帮助理解 |
| 242 | + |
| 243 | +把原文的主要思想压缩成一个简单的比喻: |
| 244 | + |
| 245 | +1. **先想象一张地图(结构空间)** |
| 246 | + - 这张地图上每个点都是一个系统版本/模型版本; |
| 247 | + - 靠近的点表示结构相似。 |
| 248 | + |
| 249 | +2. **差量 Δ 就是从一个点到另一个点的“矢量”** |
| 250 | + - 一次修复、一次定制、一次迁移,都是一个箭头 Δ; |
| 251 | + - 多次变化就是 Δ1, Δ2, … 的接力。 |
| 252 | + |
| 253 | +3. **合并 ⊕ 是箭头的“串联”运算** |
| 254 | + - Δ1 ⊕ Δ2 表示先走 Δ1 再走 Δ2; |
| 255 | + - 希望它满足封闭性、结合律等良好性质。 |
| 256 | + |
| 257 | +4. **Y = F(X) ⊕ Δ 表示:从某个模型 X 出发,先走一段由生成器 F 自动画出的路径,再加上人工画的那段 Δ** |
| 258 | + |
| 259 | +5. **可逆性是说:尽量让箭头可以反向走,或者至少搞清楚“能走回头路的是哪几类箭头”** |
| 260 | + |
| 261 | +6. **原文的批评是:我们在研究“地图上的某一个点(程序 P)性质或者特定两点之间的转换”上已经很强,但对整个地图的形状、箭头的组合与可逆性质,没有一门真正系统的理论;GRC 是在提出这样一门理论的初步轮廓。** |
| 262 | + |
| 263 | +更进一步: |
| 264 | + |
| 265 | +7. **软件世界是一本“动态地图集(Atlas)”,而非单一地图** |
| 266 | +- 现实中的复杂软件系统无法用单一视图完整描述,就像真实世界无法用一张地图呈现所有信息。 |
| 267 | +- 每个 **DSL**(领域特定语言)就是一本专题地图: |
| 268 | + - **数据库Schema图**(数据模型空间) |
| 269 | + - **业务流程图**(工作流空间) |
| 270 | + - **UI组件树**(界面空间) |
| 271 | + - **部署拓扑图**(基础设施空间) |
| 272 | +- 这些地图共同构成了一本**地图集**,每个视图都是同一现实在不同关注点/不同抽象切面的投影。 |
| 273 | + |
| 274 | +8. **Generator(F)是地图制图学中的“投影变换规则”** |
| 275 | +- 地质学家通过测量数据,**按照投影规则**生成可视化的地质图。 |
| 276 | +- 同样,**Generator** 是领域模型空间到具体实现空间的映射规则: |
| 277 | + - `F_orm`:领域模型 → 数据库Schema |
| 278 | + - `F_api`:领域模型 → API接口规范 |
| 279 | + - `F_ui`:业务对象 → 界面组件树 |
| 280 | +- 每个Generator定义了两个结构空间之间的**可计算映射关系**。 |
| 281 | + |
| 282 | +9. **差量Δ是地图上的“结构化编辑图层”** |
| 283 | +- 在传统GIS中,你不能直接在印刷地图上修改河流走向。 |
| 284 | +- 但在现代数字地图系统中,所有修改都是**分层的**: |
| 285 | + - **基底图层**:由Generator从核心模型自动生成的“标准地图” |
| 286 | + - **差量图层(Δ)**:本地化的修正、客户特定的覆盖、紧急修复 |
| 287 | +- **关键区别**:Δ不是随意涂改,而是在**同一坐标系**中的精确标记: |
| 288 | + > “在坐标(业务对象.订单.状态)处增加‘已复核’状态” |
| 289 | + > 而不是“在Order.java第123行插入代码” |
| 290 | +
|
| 291 | +10. **可逆性:地图集的“同步更新机制”** |
| 292 | +真正的挑战在于多地图协同: |
| 293 | +- **代数可逆性**:如果在地质图上标记“此处实际为沉积岩而非花岗岩”(+Δ),应该能撤销此标记(-Δ) |
| 294 | +- **变换可逆性**:从地质数据到地质图的投影(F),最好能反向从地图修改回数据(F⁻¹) |
| 295 | +- **过程可逆性**:当交通图上某条道路被封闭(Δ_traffic),这个变化可以被**反向传播**: |
| 296 | + - 自动提醒地质图:“此处可能有地质勘探受影响” |
| 297 | + - 自动更新行政区划图:“此处边界通行规则需调整” |
| 298 | + - 而不是让各地图维护者手动发现和同步 |
| 299 | + |
| 300 | +> “哪些变化需要自动传播、传播到哪几张地图”是具体工程设计的问题,不是 GRC 在理论层面直接强制的。 |
| 301 | +
|
| 302 | +这三种可逆性共同构成了GRC控制‘熵增’的机制:代数可逆确保修改可撤销(局部熵不变),变换可逆力求在表象之间保持信息守恒(跨视图熵不变),过程可逆则允许重构演化路径(全局熵可控)。 |
| 303 | + |
| 304 | +12. **Y = F(X) ⊕ Δ 在地图集中的多层含义** |
| 305 | +这个公式在不同层级递归出现: |
| 306 | + |
| 307 | +| 层级 | 公式 | 地图集比喻 | |
| 308 | +|------|------|------------| |
| 309 | +| **应用层** | App = F_app(DomainModel) ⊕ Δ_custom | 最终用户看到的“综合地图” = (标准投影 + 本地覆盖层) | |
| 310 | +| **模型层** | DSL = F_dsl(MetaModel) ⊕ Δ_dsl | “交通图规范” = (标准制图规则 + 本地图例扩展) | |
| 311 | +| **元模型层** | MetaModel' = MetaModel ⊕ Δ_meta | “整个地图集的坐标基准”自身的演进 | |
| 312 | +| **工具层** | Generator' = Generator ⊕ Δ_gen | “投影规则”本身也可以版本化和定制 | |
| 313 | + |
| 314 | + |
| 315 | +如果读者带着这个图景,再回头看原文中关于“结构空间”、“坐标系”、“可逆性”、“分形递归”等概念,以及它对类型论、范畴论当前用法的批评,就会更加透彻——GRC是在呼吁将软件研究的重心,从“单个程序的静态快照”,扩展到“整个程序族在多空间协同下的动态演化轨迹以及这些轨迹所构成的空间整体”。 |
0 commit comments