Big Ball of Mud 到底是什么?
“Big Ball of Mud”显然不是那种你会在 Steering Committee 面前,用来开启 Architecture Assessment 的术语。这个名字听起来很像开发者黑话,甚至带一点挑衅意味。要是你用它形容自己的系统,至少得准备好被追问几句。
但这个词也并不是随口编出来的。
它来自软件架构与 Pattern 文献。Brian Foote 和 Joseph Yoder 在 1997 年的 Pattern Languages of Programs Conference 上发表了 Big Ball of Mud,之后收录于 Pattern Languages of Program Design 4。此后,这个术语也不断出现在 Software Engineering 研究中,包括 Architecture Erosion 与 Design Erosion 相关工作。
不过,这并不意味着它已经成为一种形式化、标准化的诊断。
没有 ISO 定义,没有行业统一接受的测量值,也没有一个 Threshold 能够客观地说:从这里开始,一个“普通” Legacy System 就变成了 Big Ball of Mud。
它更像是一个对某种结构状态异常准确的描述。
而且最重要的是:它描述的并不是“Monolith”。
Foote 和 Yoder 所说的 Big Ball of Mud
Section titled “Foote 和 Yoder 所说的 Big Ball of Mud”Foote 和 Yoder 用一句话概括它:
“A BIG BALL OF MUD is a casually, even haphazardly, structured system.”
也就是说,这类系统的结构更多是偶然形成,甚至带有明显的随意性。系统如何组织,越来越由短期可行性决定,而不是由一套一致的设计决定。
Foote 和 Yoder 描述的是那些经历不受控制的增长、并不断通过务实修补继续演化的系统。信息可能跨越相距很远的区域共享,也可能在多个地方重复保存。整体结构要么从一开始就没有清晰定义,要么在长期变化中侵蚀到几乎无法识别。
这里同样重要的是,他们没有做什么。
他们并没有简单地把 Big Ball of Mud 当成“开发者能力差”的证据,也没有把它描述成某个开发者的道德失败。相反,他们的工作很重要的一部分,就是解释为什么这种系统会如此常见。
因为从短期看,构成这种系统的那些决定可能非常合理。
一个 Pragmatic Change 往往比结构上更干净的方案更快。绕过已有 Boundary,看起来可能比先修正 Boundary 更便宜。一个 Prototype 完成验证之后也许就继续活了下去。一个应用可以持续增长很多年,而并不是在每个时刻都值得、或经济上都适合做一次全面架构重构。
所以问题不是:每一个单独决定都必须明显错误。
真正有问题的是:所有这些决定叠加之后,系统内部结构最终变成了什么。
正是这种两面性,让 Big Ball of Mud 这个概念直到今天对我仍然有价值。
当架构失去解释力
Section titled “当架构失去解释力”后来的研究用 Architecture Erosion、Design Erosion 等术语描述了一个相关过程。
Li、Liang、Soliman 和 Avgeriou 研究了实践者如何理解 Architecture Erosion。简单地说,它描述的是这样一种过程:系统实际实现出来的结构越来越偏离原本希望实现的架构。
作者明确把其中一种可能的极端形态与 Big Ball of Mud 联系起来:系统最终失去一套仍然可以被感知、被识别的 perceivable architecture。
这个表述抓住了一个关键点。
架构并不需要在 Repository 中“物理消失”。
目录还在。Module 还在。Component、Class、Service 还在。也许甚至还有最新的架构图。不同区域有业务名称。文件系统里仍然看得见 Layer。
整个系统看起来仍然有结构。
但这套可见结构越来越不能可靠解释系统实际上如何工作。
Baum、Dietrich、Anslow 和 Müller 从 Design Erosion 的角度研究了类似过程。他们分析设计原则的违反如何在系统演化过程中产生和传播。在这种视角里,Big Ball of Mud 不是某个星期二早上突然出现的状态,而可能是持续结构恶化的最终结果。
这个过程究竟如何形成,是另一个问题。
下一篇文章会专门讨论。
但为了定义当前概念,这里先抓住另一个观察就够了:
Big Ball of Mud 描述的不是系统外部形态,而是它内部结构所处的状态。
更精确一点:
Big Ball of Mud 是这样一种状态:系统可见的、或者原本计划中的结构,越来越无法可靠解释系统实际上是怎样组织起来的。
这不是 Foote 和 Yoder 的原始定义,而是本系列采用的工作定义。它来自他们最初的描述,并结合后来关于 Architecture Erosion 与 Design Erosion 的研究。

一套架构可以在纸面上继续存在,同时它在实现中真正能够约束系统的能力越来越弱。
面向 Frontend 的工作定义
Section titled “面向 Frontend 的工作定义”把这个概念转到 Frontend,并不意味着我们要寻找某个特定技术、或者某个规模阈值。
一个 Codebase 不会因为它很大、存在很久、统一 Deployment,或者有很多开发者参与,就自动变成 Big Ball of Mud。
所有这些特征既可以存在于结构优秀的系统,也可以存在于严重侵蚀的系统。
真正关键的是:架构是否还真正发挥结构化作用。
业务边界应该把不同职责隔开。Slice 应该限制系统不同部分需要彼此知道多少。Layer 应该定义职责以及依赖方向。Interface 应该控制哪些信息可以穿过这些边界。
在 Big Ball of Mud 中,这些边界会逐渐失去作用。
业务职责开始混在一起。Slice Boundary 也许仍然存在于目录树,但越来越无法限制真实 Dependency。Layer 仍然有名字,但依赖方向不再可靠。职责在不同区域之间漂移。面对新需求,人们越来越倾向于在“技术上眼前最容易够到”的地方修改,而不是在“职责真正所属的位置”修改。
于是,架构失去它最重要的功能之一:
我们越来越无法根据系统结构判断职责在哪里,也无法从结构推断一次修改可能影响哪些地方。
因此,在本系列中,我对 Frontend 使用下面这个工作定义:
当一个 Frontend 的内部结构已经不能可靠地限制或解释修改、职责与依赖关系时,它开始进入 Big Ball of Mud 状态。
目录可能仍然叫 orders。
Component 仍然放在 presentation 下。
某一块区域仍然被称作独立业务 Slice。
真正有意思的问题只是:
这些名称还意味着什么吗?
这种状态在 Frontend 中会表现出哪些具体症状,是另外一个问题。Cyclic Dependency、God Service、Global State、Cross-Slice Dependency,以及修改后出现大范围 Side Effect,都值得更细致地分析。
本系列后面的 “在 Frontend 中识别它” 一文会专门讨论这些症状。
这里我们暂时只定义状态本身。
并不是所有难看的代码都是 Big Ball of Mud
Section titled “并不是所有难看的代码都是 Big Ball of Mud”恰恰因为这个词很形象,它也很容易被过度使用。
一个两千行的 Class 可能有问题。一个切分很差的 Component 可能代价很高。某个 God Service 可能是严重架构问题。Technical Debt 可能多年拖慢开发。
但这些单独的发现,都不足以让我们合理地把整个系统诊断为 Big Ball of Mud。
年龄也不是充分条件。
一个老系统可能包含今天看来已经过时的技术决策,同时业务结构仍然非常清晰。它的边界在很多年之后依然能够被理解。反过来,一个年轻 Codebase 也可能在很短时间里形成职责和依赖都难以理解的结构。
规模也一样。
一个大的业务模型完全可以很复杂。来自 Problem Domain 的复杂度,并不自动等于架构缺陷。拥有几千个文件的 Codebase 可以组织得非常清晰,而一个小得多的 App 也可能已经失去内部边界。
Legacy Software 同样不自动等于 Big Ball of Mud。
一个 Legacy System 可以使用过时技术,可以拥有大量 Technical Debt,但仍然由业务上清晰分离的区域组成,并且保持稳定、可理解的 Dependency。
所以 Big Ball of Mud 应该继续是一种结构性诊断,而不是我们对“不喜欢维护的软件”使用的骂人词。
这一点尤其重要,因为在 Architecture Assessment 中,有另一个词经常被拿来替代它。
Monolith 不是 Big Ball of Mud
Section titled “Monolith 不是 Big Ball of Mud”“很遗憾,我们这里是一个 Monolith。”
我在多个 Architecture Assessment 中听过这个句子的不同版本。它往往带着一种语气,好像诊断已经完成。
但从技术上说,这句话对系统内部架构质量几乎什么都没有说明。
Monolith 首先描述的是一种 System Form:应用作为一个整体开发并 Deployment。这个整体内部如何组织,并不由“Monolith”这个词决定。
一个 Modular Monolith 完全可以拥有清晰业务 Slice,可以很好地拆分职责,可以强制单向 Dependency,可以使用一致 Layer,也可以通过稳定 Internal Interface 进行协作。
它的外部 System Boundary 很大。
这并不意味着内部 Architecture Boundary 也必须很大。
一个 Deployment,不等于一种内部结构。
反过来,把系统分布出去,也不会自动产生更好的架构。一个系统可以由很多独立 Deployment 的部分组成,却仍然在业务上高度纠缠。Dependency 可以继续是 cyclic、implicit,而且几乎无法局部化——无论这些依赖发生在同一个 Process 内,还是穿过多个技术 System Boundary。
分布式不是架构质量。集中式也不是。
再短一点:
“Monolith”描述系统形态。“Big Ball of Mud”描述结构状态。
这两种属性可以同时存在。
但并不是必然同时存在。
一个结构优秀的 Monolith 完全可能存在。
一个以 Monolith 形式 Deployment 的 Big Ball of Mud 也完全可能存在。
同样,一个分布式系统在结构上也可以是 Big Ball of Mud。
Deployment Topology 与内部架构质量,是两个不同的坐标轴。

系统是 Monolith 还是分布式,以及它内部结构到底好不好,是两个不同问题。
项目里说“Monolith”时,有时真正想表达的是别的东西
Section titled “项目里说“Monolith”时,有时真正想表达的是别的东西”这样一来,Assessment 中那句“我们这里很遗憾是一个 Monolith”就变得更有意思了。
因为说这句话的人,很多时候并不主要在描述外部 System Form。
再追问几句,也许会发现他用“Monolith”真正表达的是:修改有难以预测的 Side Effect;业务边界几乎已经不起作用;没人能确定一次修改会影响哪些区域;某些人掌握关键知识,没有他们就很难安全修改大部分系统;一个业务上很小的变化,可能意外传播到整个应用。
这时,“Monolith”实际上成为一种概念缩写。
这并不是欺骗。
恰恰相反,在项目内部,这个词也许已经用了很多年,逐渐成为一整套结构性体验的诊断代称。
负责人说:
“我们有一个 Monolith。”
他脑中可能真正表达的是:
这个系统已经很难再拆成彼此边界明确的区域。
而外部 Architect 听到的,则完全正确地是:
这个应用作为一个整体开发并 Deployment。
两边使用同一个词。
但脑子里想的是完全不同的系统。
对于一个在 App 里工作多年的开发者,“Monolith”这个词可能已经自动携带了所有关于危险修改、Side Effect 和 implicit Dependency 的经验。
对外部 Assessor 来说,它并没有这些附加含义。
于是,一个复杂的结构状态,被压缩成了一个技术上相对中性的架构术语。
这个过程中可能丢失大量信息。
当一个正确的词,把人带向错误诊断
Section titled “当一个正确的词,把人带向错误诊断”如果这种概念模糊没有实际后果,它就只是语言上的小问题。
但 Assessment 几乎总是受限的。
时间有限。Budget 有限。文档不完整。不是每个相关人员都可用。不是每个 Codebase 都能在几天内做深度分析。有些 Assessment 甚至有意更侧重 Interview、架构文档和组织流程,而不是深入到实际 Dependency Structure 的代码分析。
在这些条件下,如果所有 Interview 不断重复“Monolith”,完全可能形成一个很合理的判断:
一个有经验的 Architect 知道,Monolith 本身不是架构问题。
这完全正确。
只是他可能回答了另一个问题,而不是最重要的问题。
我自己曾在一次 Assessment 中经历过这种情况。
当时我已经有自己的系统判断,但想刻意获得第二个视角。于是我邀请了一位 Principal Architect,并且没有提前告诉他我的结论,希望他尽量不受我的 Bias 影响来观察系统。
他的 Assessment 很大一部分来自对各参与角色的 Interview。Codebase,尤其是它真实的 Dependency Structure,并没有同等深入地分析。
Interview 中反复出现一个词:
“Monolith。”
最后的判断大致是:
其实没有那么糟。
仅仅从听到的描述看,这个判断完全可以理解。
毕竟 Monolith 本身并不构成结构性灾难。
但深入查看真实 Dependency 后,画面完全不同。在表面上可见的 Module 后面,是高度纠缠的结构:大规模 cyclic 区域和大量 Dependency。大家在谈论的“架构”,只能有限解释系统里真正起作用的结构。
这是我的个人经验,并不是科学证据,不能因此得出“Interview-based Assessment 天生不可靠”的结论。
但它非常清楚地展示了一个方法论风险:
“Monolith”可能无意中成为错误诊断。
不是因为这个词错。
而是因为它回答的是另一个问题。
所以当多个 Interviewee 都说“Monolith”时,我首先关心的不是:应该不应该把它拆成更小 Deployment。
我更想问:
- 系统内部的业务边界真的还有效吗?
- 文档中的架构与实际实现的架构一致吗?
- Dependency 是否遵循可理解的方向?
- 职责是否还能清晰定位?
- 修改是否通常保持在预期边界内?
- 是否存在很大的相互依赖区域?
- 可见结构能否解释系统哪些部分实际上彼此依赖?
只有这些答案,才能帮助我们做真正重要的区分:
我面对的是一个大型、模块化的 Monolith?
还是一个只是恰好以 Monolith 方式 Deployment 的 Big Ball of Mud?
把两者混为一谈,既可能低估其中一个,也可能冤枉另一个。
外部形态不是诊断
Section titled “外部形态不是诊断”Big Ball of Mud 这个词很强烈。
也正因为强烈,更应该谨慎使用。
它不是简单描述旧软件、很多代码、Monolith,或者拥有 Technical Debt 的应用。
它描述的是结构状态。
Foote 和 Yoder 在 1990 年代末就描述了那些更多由务实增长、而不是一致设计形成的系统。Architecture Erosion 与 Design Erosion 的研究又补充了另一个视角:系统实际实现的结构,可能随着时间不断偏离最初设计的架构。
对 Frontend 来说,这并不意味着违反一条 Layer Rule 就已经成为 Big Ball of Mud。也不意味着所有规模大、寿命长的应用最终必然走到这里。
关键仍然是:架构还在履行它的结构化功能吗?
Slice 是否仍然限制业务职责?
Layer 是否仍然有可靠含义?
Dependency 是否仍然可以理解?
我们还能不能通过系统可见结构判断一次修改应该发生在哪里,以及它可能影响哪些部分?
这些判断越不可靠,架构的实际意义就越弱。
最终,架构只剩下标签。
而如果 Big Ball of Mud 并不等于 Monolith,甚至原本结构清晰的系统也可能逐渐进入这种状态,那么很自然会出现下一个问题:
是谁把它建成这样的?
答案往往没有想象中那么令人满意。
没有哪个人某天早上上班时决定:“今天我要建一个 Big Ball of Mud。”
这正是下一篇文章的主题:
“没有人建造它。”
Foote, Brian; Yoder, Joseph W.: Big Ball of Mud. Originally presented at the Fourth Conference on Pattern Languages of Programs (PLoP ’97), 1997; later published in: Harrison, Neil; Foote, Brian; Rohnert, Hans (eds.): Pattern Languages of Program Design 4. Addison-Wesley, 2000. Original: https://www.laputan.org/mud/
Li, Ruiyin; Liang, Peng; Soliman, Mohamed; Avgeriou, Paris: Understanding Architecture Erosion: The Practitioners’ Perceptive. IEEE/ACM International Conference on Program Comprehension (ICPC), 2021. DOI: 10.1109/ICPC52881.2021.00037. Preprint: https://arxiv.org/abs/2103.11392
Baum, David; Dietrich, Jens; Anslow, Craig; Müller, Richard: Visualizing Design Erosion: How Big Balls of Mud are Made. IEEE Working Conference on Software Visualization (VISSOFT), 2018, pp. 122–126. DOI: 10.1109/VISSOFT.2018.00022. Preprint: https://arxiv.org/abs/1807.06136