没有人建造了它
没有人会以“十年后让系统影响范围几乎无法预测”为目标开始一个软件项目。模糊职责、跨越很远的依赖关系,或者只有少数资深开发者还能理解的区域,从来不会作为目标架构写进设计文档。
项目开始时通常有更合理的东西:一个问题、一个想法、一个客户、一段市场机会、一个 Deadline,或者一个技术难度很高的 Proof of Concept。人们只是想先让某件事情工作起来。
上一篇文章讨论了 Big Ball of Mud 到底是什么:它不是 Monolith、Legacy Software 或 Technical Debt 的同义词,而是一种结构状态——系统原本设计或表面可见的架构,越来越无法可靠解释系统实际如何组织。由此几乎自然会出现下一问:如果没有人主动追求这种状态,系统为什么还是会走到这里?
最简单的解释,是讲一个“差开发者、短视 Management、缺少架构能力”的故事。
更有意思的解释要困难得多:
Big Ball of Mud 很少是被有意识建造出来的。它可以在多年中,由大量在当时局部上下文里合理、有用甚至成功的决策逐步形成。
先做一个编辑上的说明:这一篇比系列前几篇长。不是因为答案需要人为复杂化,而是因为寻找“唯一原因”本身很容易误导。进入 Big Ball of Mud 的路径并不只有一条。有的系统在多年中因为不断叠加局部合理决策而缓慢侵蚀;有的系统一开始就以问题状态被新团队接手,之后又在完全不同的合同和组织条件下继续发展;还有的系统从一个成功 Prototype 演化成长期产品,却从未真正完成从探索性代码到长期系统的结构转换。
这些路径很不同,但它们共同指向一个问题:谁在时间维度上真正拥有整个系统的结构责任,而且这种责任是否真的有能力产生效果?
有时,故事从成功开始
Section titled “有时,故事从成功开始”Brian Foote 和 Joseph Yoder 在描述 Big Ball of Mud 时,并非偶然从 Throwaway Code 和 Permanent Prototype 讲起。
Prototype 的目标与长期产品不同。它要回答的是:这个想法能不能成立?算法可不可行?某个技术难题是否可解?某种 Concept 能不能演示?
为了回答这些问题,Prototype 可以做出一个正式产品不一定应该做的假设。它可以把知识直接写进代码,跳过抽象,甚至采用长期可维护性并不重要的结构。这不是错误,而是 Exploration 的目的。
真正有意思的是 Prototype 成功之后。Demo 说服了别人,客户产生兴趣,有人看到了市场机会,于是那份代码突然拥有了经济价值。里面已经沉淀了知识、开发时间和一个能够工作的解决方案。
Foote 和 Yoder 描述的危险正是在这里:Throwaway Code 没有被丢掉,因为另一条路看起来意味着“把一个已经工作的东西再做一遍”。他们并不是说 Prototype 必然产生坏产品。很多时候,在团队真正理解 Domain 之前,探索本来就是必要的。问题只发生在:从 Experiment 进入长期产品的那一刻,结构没有随系统身份一起升级。
Proof of Concept 的成功,可能恰恰成为它再也不会被丢掉的原因。 成功会保存早期决策。
我自己也经历过类似故事。起点是一项科学和技术上都非常困难的想法。一位非常优秀的科学家解决了核心难题,并证明了技术可行性,这项工作本身令人印象深刻。随后,一位企业家看到了它的商业潜力,而且这个判断也是正确的。于是 Proof of Concept 变成了产品。
在哪一刻应该说:“Big Ball of Mud 从这里开始”?
至少不是这里。
局部正确的决策
Section titled “局部正确的决策”当 Prototype 变成成功产品,环境就改变了。客户在等 Feature,Incident 必须解决,合同里有日期,团队也许还没有掌握一个更合适的新技术方案。一个“真正干净”的解决方案可能要求先重构现有结构,而一个 Workaround 今天就可以完成。
在这样的条件下,Workaround 完全可能是正确决定。
软件开发不能只为假想未来优化。公司必须交付,用户需要可用产品,一个结构完美但错过市场窗口的方案,在经济上可能更差。Architectural Technical Debt 研究也反复指出这种双重性:某个架构决定在当时已知约束下完全可以成立,直到几年后才成为进一步演进的障碍。
所以,问题不是 Pragmatism。
更重要的问题是:如果局部优化长期没有足够强的结构反馈,会发生什么?一次捷径不会产生 Big Ball of Mud,一个 Workaround 也不会。甚至主动接受 Technical Debt 都可以是理性的投资决定。
真正危险的是成千上万条从未再被撤回的捷径,最终自己变成了架构。
没有被撤回的东西,最终会成为架构
Section titled “没有被撤回的东西,最终会成为架构”第一个 Sonderfall 通常仍然很明显是 Sonderfall。也许旁边甚至写着:
TODO: Release 后整合。
但 Release 之后是下一个 Feature,再之后是下一个客户,随后又是下一个 Deadline。与此同时,另一个修改已经开始依赖这条特殊逻辑,甚至第二个团队已经把新的行为建立在它之上。
于是,“暂时这样做”逐步变成“我们这里就是这么工作的”,最后变成“我们一直都是这么做的”。
这正是 Architecture Erosion 的关键机制之一:
架构不仅由我们主动设计的东西组成,也由那些多年后从未被撤回的临时决定组成。
Foote 和 Yoder 的 Piecemeal Growth 并没有把渐进式开发描述成错误。活着的软件本来就必须一边运行,一边被一点点扩展。真正的风险在于:紧急的局部要求不断挤掉长期架构诉求。因此他们把 Expansion 与持续出现的 Consolidation Phase、Refactoring 和 Repair 放在一起讨论。
并不是每个 Feature 之后都需要“大扫除”。但长期系统必须拥有某种机制,让内部结构能够回应新学到的东西。需求会变化,团队会学习,技术平台会发展,旧假设会被证明错误,业务边界也可能移动。如果系统周围的一切都在演进,而内部结构永远只追加、不重新评价,那么它会越来越像过去的沉积物。
运营压力会缩短决策时间尺度
Section titled “运营压力会缩短决策时间尺度”Deadline 不会自动摧毁架构,但它会改变某个时刻什么方案看起来合理。
在 Operational Pressure 下,团队关注的时间跨度会缩短。问题不再是“这个区域三年后应该是什么结构”,而变成“什么东西星期五之前能可靠上线”。这完全可能合理。
关于软件工程中的时间压力,研究也并没有给出“压力越大软件必然越差”这种简单公式。Kuutila、Mäntylä、Farooq 和 Claes 的系统性综述强调,压力的类型、强度和上下文都会影响结果。问题在于,当短期压力长期存在时,组织可能一直在以“特殊情况”的决策模式工作。
如果每个 Sprint 都是例外,每次 Release 都是紧急状态,那么短期优化就不再是例外,而开始成为默认架构机制。
这会让团队不断把结构性工作推迟到一个永远不会到来的“稍后”。
偶尔为了 Deadline 做妥协是 Pragmatism。长期没有任何 Consolidation,则会让 Pragmatism 变成结构。

一个 Big Ball of Mud 不一定始于一次糟糕决定。真正重要的是,大量局部决定如何随着时间共同作用。
谁在代表结构?
Section titled “谁在代表结构?”一个系统能否持续抵抗局部优化,不只取决于开发者是否“在乎架构”。
必须有人长期代表系统结构:提出 Boundary 问题,观察 Dependency,保留技术方向,推动重要 Refactoring,并能够在“局部最快”和“长期可变更”之间产生真实影响。
这不一定是一个叫“Architect”的职位。可以是 Tech Lead、Staff Engineer、一个稳定团队,或者通过明确规则和 Review 共同承担的机制。
关键不是头衔,而是 Continuity 和 Mandate。
如果这份责任每几个月都换人,或者只有建议权、没有实际影响力,那么结构很容易在一系列局部交付中被消耗。
架构责任必须比下一张 Ticket 活得更久。
否则没人真正拥有“系统五年后还是否能被理解”这个问题。
一个领域里的卓越,并不能替代另一个领域
Section titled “一个领域里的卓越,并不能替代另一个领域”成功产品经常来自非常强的专业能力。
一位优秀科学家可以解决极难算法问题,一位优秀业务专家可以精确理解 Domain,一位非常强的 Backend 工程师可以设计高性能服务,一位资深项目经理可以让大型项目按时交付。
这些能力都很重要。
但它们不能互相自动替代。
擅长算法不自动意味着擅长长期软件架构;擅长 Delivery 不自动意味着能够判断 Dependency Structure;熟悉业务并不意味着天然会设计可演进的模型;资深 Backend 工程师进入 Frontend,也需要理解另一套 Runtime 和 Reactive Model。
组织的问题往往不是缺少聪明的人。
而是默认“某种技术卓越会自动覆盖另一种专业能力”。
如果一个系统长期缺少真正承担结构责任的人,再优秀的局部实现也可能不断积累成整体失配。
当团队开始对抗平台
Section titled “当团队开始对抗平台”架构侵蚀的另一个来源,是系统越来越依赖自己的替代机制。
Framework 提供 Router,团队却建立另一个 Routing Layer。Forms 有自己的 State Model,团队又维护第二套 Validation State。Runtime 已经提供 Change Detection 或 Reactive Primitive,系统却通过手工同步保证界面一致。平台原本提供的能力被一层层“更可控”的内部方案覆盖。
每个 Workaround 单独看都可能有理由。
但一旦这些方案积累起来,系统真正运行的模型就越来越不再是 Framework 模型,而是组织自己多年形成的一套隐式规则。
这会让 Upgrade、Onboarding、Debugging 和未来迁移越来越贵。
一个 Framework 不是因为版本旧才会成为 Legacy。一个团队也可以在最新版本里长期与 Framework 的基本范式对抗。
团队会离开,决定会留下
Section titled “团队会离开,决定会留下”长期系统有一个重要的不对称性:人的任期通常比代码短。
开发者会换团队,Product Owner 会变化,架构师会离开,公司会重组,供应商会更换。
但过去的技术决定仍然存在。
Martin Robillard 对 Turnover-Induced Knowledge Loss 的研究关注的正是这种现象:离职不仅减少人力,也会带走那些没有被代码、文档、测试或明确结构保存下来的经验知识。
如果一个系统高度依赖“知道为什么这里不能改”的历史记忆,那么人员流动会逐步把 Architecture Rationale 从显式知识变成考古学。
新团队看到的是结果,而不是当时约束。
他们可能会合理地延续旧结构,因为不知道哪些部分是偶然、哪些是必要。
或者反过来,他们会“清理”一个看起来多余的 Sonderfall,却直到 Production 才发现它承载了十年前某个真实业务规则。
代码可以比它的解释活得更久。
有时,我们直接继承一个 Big Ball of Mud
Section titled “有时,我们直接继承一个 Big Ball of Mud”并不是每个团队都参与了系统从 Prototype 到泥球的全过程。
有时新的团队或供应商接手时,结构问题早已存在。
这会改变责任问题。
一个新团队可以很快识别大量问题,却仍然只有一个非常具体的 Delivery Auftrag:实现 Version 2、增加 Feature X、迁移到新浏览器、或者保持系统继续运行。
如果合同没有预算重建核心结构,团队很难把所有历史 Technical Debt 作为自己的“顺带任务”解决。
这并不意味着它应该继续制造新问题。但它说明:识别问题和拥有解决问题的 Mandate 是两件不同的事。
你可以继承架构问题,却并不会自动继承改变整个系统的预算和决策权。
一个项目结束了,软件却没有结束
Section titled “一个项目结束了,软件却没有结束”软件生命周期经常比项目生命周期长。
项目被立项、预算、执行、验收并正式结束。
软件却继续存在。
一年后新的 Requirement 出现,于是出现新项目。再过两年换供应商。之后还有 Migration、监管修改、Security Update 和其他 Change Request。
从组织视角看,这些是多个独立项目。
从代码视角看,它们仍然修改同一个系统。
这形成一个深刻冲突:组织可以把责任切成项目,而软件不会自动接受这些切分。
如果没有一个跨项目持续存在的 Architecture Ownership,那么每个项目都可能局部合理地完成自己的目标,却没有人长期负责整体结构。

项目和合同边界切分的是组织,而产品的技术历史会完整跨越这些边界。
公共采购让这个冲突特别明显
Section titled “公共采购让这个冲突特别明显”公共部门的信息系统尤其容易暴露这种时间尺度差异。
采购合同有明确范围、预算、验收和供应商。可软件在合同结束后通常还会继续运行很多年。
相关研究讨论了 Requirements Specification 中的制度冲突、公共 IT Outsourcing 的知识损失,以及长期可持续软件所需要的 Ownership。
问题并不在“外部供应商”这个概念本身。
外部团队可以交付非常好的软件,长期伙伴关系也完全可以建立高质量结构。
真正困难的是,当 Contract Boundary 与 System Boundary 不一致,而且没有机制维护跨 Contract 的结构连续性。
供应商 A 对 V1 负责,供应商 B 对 V2 负责,内部项目管理负责验收,业务部门负责需求。
那么谁对“V1 到 V5 之间系统结构的长期演进”负责?
如果答案不明确,责任空隙就会直接进入代码。
两次采购之间,谁拥有架构?
Section titled “两次采购之间,谁拥有架构?”这是一个经常被忽视的问题。
一个项目里可以有 Architect,一个供应商里可以有 Tech Lead,但项目结束后,这个角色对应的长期责任是否仍然存在?
如果下一次招标重新定义 Scope、团队和供应商,而旧系统结构只是“现状”,那么 Architectural Knowledge 很容易变成移交文档里的附件,而不是活着的决策能力。
这也是为什么长期 System Ownership 如此重要。
不是为了建立一个中央架构权威,而是为了确保某些问题不会每次都从零开始:哪些 Boundary 必须保持?哪些 Debt 是有意接受的?哪些 Workaround 应该消失?哪些风险被组织主动承担?
如果这些问题没有持续 Ownership,下一批人只能从代码中重新猜。
Outsourcing 不是问题本身
Section titled “Outsourcing 不是问题本身”把这一切简单归因于 Outsourcing 同样不准确。
内部团队也会重组,也会丢失知识,也会在时间压力下局部优化,也会建立 Big Ball of Mud。
外部供应商并不天然缺乏质量意识;内部团队也不天然拥有长期视角。
真正关键的是激励、合同、决策权、知识转移和责任持续时间是否支持长期系统健康。
如果 Auftrag 只支付 Feature,而不支付结构工作,那么供应商很难长期免费承担系统重建。
如果每次供应商更换都伴随显著知识损失,也不能简单指望下一家公司“自己读代码就懂”。
问题不在组织边界存在。
问题在组织边界是否与系统责任匹配。
当没人为 Refactoring 付钱
Section titled “当没人为 Refactoring 付钱”“为什么当时不顺便 Refactor?”从多年后回看很容易提出。
但 Refactoring 仍然消耗容量、风险预算和决策空间。
如果项目合同只验收 Feature,如果 Bonus 只看日期,如果 Product Roadmap 从不为结构工作留下空间,那么“不做 Refactoring”可能是系统激励下完全可预测的结果。
这不是说开发团队可以借此免除质量责任。
团队仍然应该避免不必要的 Debt,应该让结构成本透明,也应该提出更安全的方案。
但组织如果长期只为可见 Feature 付费,也会塑造最终得到的架构。
如果结构工作永远没有预算,那么结构最终会被当作免费的资源消耗。
两条路径——同一个盲点
Section titled “两条路径——同一个盲点”至此可以看到两种不同的 Big-Ball-of-Mud 路径。
第一种是连续演化:同一个产品从 Prototype 开始,在不断成功和局部优化中逐步侵蚀。
第二种是责任断裂:系统跨项目、供应商或团队存活,每个参与者都只拥有局部 Scope,没有人持续拥有整体结构。
两条路径不同。
共同盲点却很接近:长期 Architecture Ownership 不足。
在第一种路径里,结构责任被短期 Delivery 持续挤压;在第二种路径里,结构责任不断在组织边界之间掉落。
结果都可能是:系统活得比任何一个负责它的人更久。
当过去开始决定下一次选择
Section titled “当过去开始决定下一次选择”架构侵蚀还会产生一个自我强化循环。
系统越难修改,下一次 Deadline 下进行局部优化就越有吸引力。
因为“真正修好”意味着先理解更大的影响范围、承担更多 Regression、协调更多人。
于是团队更可能再次选择局部 Workaround。
这个 Workaround 又进一步扩大未来的复杂度。

局部解决方案会改变未来决策:系统越难修改,下一次再次局部优化的压力就越大。
过去并不完全决定未来,但它会改变未来选择的价格。
现在,局部决策还能变得更快
Section titled “现在,局部决策还能变得更快”Coding Agent 首先改变的是速度。
Agent 可以以非常高的速度找到一个局部可工作的解决方案。如果 Ownership、Boundary 和架构规则清晰,这是一种巨大优势。
如果这些方向标不存在,Agent 仍然必须在每个任务里重新猜:许多可能的局部解法中,哪一个才符合系统长期结构?
Agent 可以非常快地找到好的局部方案。而“局部方案持续获胜”本来就是历史问题的一部分。
这个系列会在关于 AI 的文章中进一步展开这一点。
讲完这些故事,很自然会问:到底是谁的错?
“没人”是一个舒服的答案,但它会完全消解责任。
决策当然有责任主体。开发者负责自己的修改,技术领导负责方向,Management 负责优先级,客户和 Auftraggeber 负责需求与合同,供应商负责约定范围内的交付质量。
但“所有人都有错”同样没有帮助。
不同的人在不同时间拥有不同信息、约束、预算和时间尺度。写 Workaround 的开发者知道本周 Deadline,却不可能知道未来五年的所有需求;Product Owner 看见正在等待 Feature 的客户,却不一定看见每一条技术依赖;架构师了解长期目标,却可能没有 Mandate 对抗每一个短期优先级;新供应商看到结构问题,却可能只有下一版产品的预算。
Project Management 与 Software Architecture 也是不同能力。一个优秀项目经理可以很好完成自己的职责,却未必拥有判断复杂 Dependency Structure 或 Test Strategy 的技术深度。这不是个人失败,而是角色边界与所需 Expertise 的问题。
所以,Big Ball of Mud 的整体状态通常不能有意义地压缩到某个人或某次决策上。
这正是“责任”和“归罪”的区别:我们可以严肃分析决策,而不必把系统演化写成犯罪故事。
没有人建造了它
Section titled “没有人建造了它”今天回看那些促使我写这组文章的系统,我很难找到一个明确时刻,说:“就在这里,它变成了 Big Ball of Mud。”
成功的 Proof of Concept 不是那个时刻;没有它,产品可能根本不会存在。把它产品化也可能是完全正确的经济决定。第一个 Workaround 多半解决了真实问题,技术切换可能有合理理由,被推迟的 Refactoring 也可能确实输给了当时更重要的客户需求。
对于继承来的系统,这个问题更难。公司 B 在 V1 上继续开发 V2,如果 Auftrag 本来就只要求交付 V2,它“有错”吗?公司 A 当初完成了被委托、验收并付费的质量要求,它“有错”吗?如果项目经理根本不拥有跨多个采购周期管理 Architecture Quality 的角色和能力,又该如何评价?
这就是 Big Ball of Mud 的形成如此值得研究的原因:
它不是一个错误,而是多年中大量局部逻辑决策相互作用的结果。
有时还要再加一句:
组织可以把责任切成项目,但软件系统不会自动接受这些切分。
Foote 和 Yoder 在 1990 年代末就提出:究竟是什么力量,会让优秀程序员仍然产生结构上问题严重的系统?他们描述的 Throwaway Code、Piecemeal Growth、Keep It Working、Shearing Layers、Consolidation 以及最终 Reconstruction,更像是对长期软件演化力量的描述,而不是个人错误清单。
今天关于 Architecture Erosion、Architectural Technical Debt、Outsourcing 和公共信息系统的研究又扩展了这一视角:技术因素和非技术因素共同作用,短期和长期目标竞争,交接会造成知识流失,采购制度可能产生目标冲突,而长期 System Ownership 具有重要价值。
因此,Big Ball of Mud 可以被理解为一种 Emergent Result:很多参与者在很长时间里不断修改同一个系统,而每一次修改都在回应前一次修改留下的环境。团队、合同和组织会更换,技术决定却继续存在。如果缺少持续结构反馈,最后得到的整体形态可能从未被任何人作为目标图画出来。
每个人也许贡献过一点,有些人只是继续维护已经存在的一点。
没人画过最终结果,也没人某天决定“从今天开始取消边界”。
但仍然有很多人——通常出于完全可以理解的原因——各自贡献了其中一小块。
Architecture Erosion 不是事件。
它是过程。
而组织能够做出的最重要对策之一,可能非常朴素:
确保架构责任活得比下一个 Feature、下一个项目、下一个团队和下一份合同更久。
- Brian Foote, Joseph W. Yoder: Big Ball of Mud. Technical Report WUCS-97-34, Department of Computer Science, Washington University, ursprünglich vorgestellt auf der Fourth Conference on Pattern Languages of Programs (PLoP ’97), 1997; später als Kapitel 29 in Neil Harrison, Brian Foote, Hans Rohnert (Hrsg.): Pattern Languages of Program Design 4. Addison-Wesley, 2000.
- Ruiyin Li, Peng Liang, Mohamed Soliman, Paris Avgeriou: Understanding Software Architecture Erosion: A Systematic Mapping Study. Journal of Software: Evolution and Process 34(3), 2022, e2423. DOI:
10.1002/smr.2423. - Antonio Martini, Jan Bosch, Michel Chaudron: Investigating Architectural Technical Debt Accumulation and Refactoring over Time: A Multiple-Case Study. Information and Software Technology 67, 2015, S. 237–253. DOI:
10.1016/j.infsof.2015.07.005. - Roberto Verdecchia, Philippe Kruchten, Patricia Lago, Ivano Malavolta: Building and Evaluating a Theory of Architectural Technical Debt in Software-Intensive Systems. Journal of Systems and Software 176, 2021, 110925. DOI:
10.1016/j.jss.2021.110925. - Miikka Kuutila, Mika Mäntylä, Umar Farooq, Maëlick Claes: Time Pressure in Software Engineering: A Systematic Review. Information and Software Technology 121, 2020, 106257. DOI:
10.1016/j.infsof.2020.106257. - Rodrigo Rebouças de Almeida, Christoph Treude, Uirá Kulesza: What’s Behind Tight Deadlines? Business Causes of Technical Debt. IEEE/ACM International Conference on Cooperative and Human Aspects of Software Engineering (CHASE), 2023.
- Robert Ramač et al.: Prevalence, Common Causes and Effects of Technical Debt: Results from a Family of Surveys with the IT Industry. Journal of Systems and Software 184, 2022, 111114. DOI:
10.1016/j.jss.2021.111114. - Martin P. Robillard: Turnover-Induced Knowledge Loss in Practice. ESEC/FSE ’21, 2021, S. 1292–1302. DOI:
10.1145/3468264.3473923. - Maria Alaranta, Sirkka L. Jarvenpaa: Changing IT Providers in Public Sector Outsourcing: Managing the Loss of Experiential Knowledge. 43rd Hawaii International Conference on System Sciences (HICSS), 2010. DOI:
10.1109/HICSS.2010.101. - Reetta Ghezzi, Aapo Koski, Janne Lautanala, Mikko Lehtisalo, Manu Setälä, Tommi Mikkonen: Towards Sustainable Software for Public Sector Information Systems. IEEE/ACM International Conference on Software and System Processes (ICSSP), 2023, S. 86–91. DOI:
10.1109/ICSSP59042.2023.00019. - Carl Erik Moe, Michael Newman, Maung Kyaw Sein: The Public Procurement of Information Systems: Dialectics in Requirements Specification. European Journal of Information Systems 26(2), 2017, S. 143–163. DOI:
10.1057/s41303-017-0035-4. - Josep M. Marco-Simó, Joan A. Pastor-Collado: IT Outsourcing in the Public Sector: A Descriptive Framework from a Literature Review. Journal of Global Information Technology Management 23(1), 2020, S. 25–52. DOI:
10.1080/1097198X.2019.1701357. - Casper Lassenius, Parastoo Mohagheghi, Jefferson Seide Molléri: Bringing it Home: Successful Backsourcing of Software Development in the Public Sector. Empirical Software Engineering 30, 170, 2025. DOI:
10.1007/s10664-025-10722-1.