如何走出 Big Ball of Mud?
Martin Fowler 在 Refactoring 中说明,现有 Code 可以通过小步、保持行为的改变持续改善内部结构。Michael Feathers 在 Working Effectively with Legacy Code 中进一步展示了如何重新获得对难以改变的 Legacy Code 的控制:先建立 Test,通过 Seam 打破危险 Dependency,让未知行为逐步变成可验证行为。
这些方法今天依然是 Legacy Work 的核心基本功。
但成熟的 Big Ball of Mud 还有一个额外问题。
它已经不只是一些“应该 Refactor 的坏 Code”。
它是一个技术、业务、组织和历史共同稳定下来的 Socio-Technical System。很多 Boundary 之所以存在或不存在,不只是因为过去某个 Developer 写了什么,还因为 Requirement、Ownership、Team Structure、Contract、Key Person、Release Process 与 Product Decision 多年来共同作用。
因此,真正离开 Big Ball of Mud 的过程不能只叫 Cleanup。
你需要重建的不是文件夹,而是系统对 Responsibility 和 Change 的表达能力。
三种 Responsibility,而不是一个 Hero
Section titled “三种 Responsibility,而不是一个 Hero”可持续 Reconstruction 至少需要三种 Responsibility 同时存在。
这并不意味着一定要三个 Role,也不意味着必须建立大型 Governance。它只是说明:技术正确本身不足以完成长期改变。
技术 Responsibility
Section titled “技术 Responsibility”必须有人能够理解现有 Dependency、State Flow、Runtime、Testability 和 Migration Risk,并提出一个可以逐步实施的 Target Direction。
这包括:哪些 Cycle 必须消失,哪些 Public Contract 应稳定,哪些 Global State 需要 Ownership,哪些 Legacy Bridge 可以临时存在,哪些新 Slice 可以独立被验证。
Technical Responsibility 还意味着控制 Migration Size。不是设计一个“最终完美图”,而是把下一步切得足够小,使 Team 可以理解、测试和回滚。
业务 Responsibility
Section titled “业务 Responsibility”必须有人能够回答:哪些 Behavior 真正是业务要求?哪些 Sonderfall 今天仍然有效?哪些历史条件只是 Accident?哪些概念属于同一个 Capability,哪些不属于?
如果这部分缺失,Developer 只能从旧 Code 推断 Domain。
而旧 Code 恰恰可能已经把历史 Workaround、Bug、已废弃 Requirement 与真实 Business Rule 混在一起。
Business Responsibility 因此不是“给技术 Team 写 Acceptance Criteria”。
它是在 Reconstruction 中重新确认:未来系统究竟应该保护什么语义。
组织 Responsibility
Section titled “组织 Responsibility”必须有人提供时间、Priority、Decision Right 和足够稳定的 Ownership。
如果 Team 今天建立新 Boundary,明天另一个 Priority 又允许所有人绕过它,那么 Reconstruction 只会产生新的 Architecture Layer,旧力量仍然继续工作。
Organisational Responsibility 还包括真实接受 Transition Cost:一段时间内新旧路径并存、Test Investment 增加、某些 Feature 需要更多时间、Decommission 也需要 Capacity。

只拥有技术 Responsibility,Team 可能设计出一个没人确认业务含义的漂亮模型。
只拥有业务 Responsibility,Code 仍然没有可执行 Boundary。
只有 Management Mandate 而缺少技术和 Domain Competence,则很容易变成大型 Transformation Programme:很多 Roadmap、很多 Workstream,但新的系统继续复制旧问题。
Big Ball of Mud 的 Ausweg 不是一个优秀 Architect 的 Hero Story,而是一组 Responsibility 真正对齐的结果。
Big Ball of Mud 不是 Cleanup Project
Section titled “Big Ball of Mud 不是 Cleanup Project”Cleanup 很诱人,因为它可见。
Rename Service、Split Component、移动 Folder、统一 Naming、删除 Dead Code,这些都可能有价值。
但它们不会自动恢复 Architecture。
如果 Source of Truth 仍然有三个,移动文件不会减少 State Ambiguity。
如果 Business Rule 仍然没人拥有,把 Method 移到 Domain Folder 也不会让 Ownership 变清楚。
如果新 Component 仍然直接调用五个 API,拆成三个 Component 只是让 Orchestration 分散到更多文件。
所以真正的问题必须换一种问法:
- 哪个 Capability 在这里?
- 谁拥有它的 State?
- 哪些 Contract 是边界?
- 哪些 Dependency 必须最终消失?
- 哪些旧 Flow 必须被 Decommission?
- 新系统怎样让下一次 Change 重新具有 Locality?
目标不是让 Code “看起来干净”。
目标是让系统再次能够预测:Responsibility 在哪里、Change 会影响哪里。
两种不同起点
Section titled “两种不同起点”并不是每个 Team 都有正式 Modernization Mandate。
有时 Organisation 明确投入 Budget 和时间进行 Reconstruction。
更多时候,Team 只是继续做 Feature、Bugfix 和 Mandatory Upgrade,同时知道底层结构越来越危险。
这两种 Situation 不能使用同一种 Strategy。
没有 Mandate 时假装自己正在进行全面 Rewrite,会把个人 Architecture Ambition 偷偷塞进 Product Delivery。
有 Mandate 时又只做零散 Refactoring,则可能浪费一次难得的结构改变窗口。
因此,第一步不是选 Pattern。
而是明确自己到底处于哪种 Situation。
Situation 1:没有显式 Modernization Mandate
Section titled “Situation 1:没有显式 Modernization Mandate”这并不意味着什么都不能做。
Developer 仍然可以在现实 Change 中降低新的 Coupling,建立 Test,显式化 State Ownership,阻止 DTO Leakage,用 Facade 隔离旧接口,或者在新 Feature 中建立一个小而完整的 Slice。
但 Strategy 应该 Tactical。
它必须围绕 Product Work 发生,而不是假设 Team 获得了重建全部系统的授权。
一个实际 Feature 可以有两个 Implementation Path:继续沿旧结构扩展,或者把 Feature 当作恢复一个 Boundary 的机会。
关键是透明。
如果第二种 Path 比第一种需要更多 Effort,Organisation 应该知道为什么,而不是把 Architecture Work 永远隐藏在 Estimate 中。
没有 Modernization Programme,不等于没有 Architecture Decision。
只是 Decision 必须更小、更局部,并且与真实需求结合。

Opportunistic Reconstruction 之前先有 Architecture Guideline
Section titled “Opportunistic Reconstruction 之前先有 Architecture Guideline”“顺手 Refactor”只有在 Team 对目标方向存在共同理解时才会累积成 Architecture。
否则 Developer A 为了“解耦”抽一个 Facade;Developer B 用 Global Store 统一 State;Developer C 再建立 Shared Service;每个人的局部代码都比以前整洁,但整体出现三套不同的 Direction。
所以,即使没有大型 Modernization,也应该先定义少量 Architecture Guideline。
它们不需要长篇文档。
更重要的是明确而可检查。例如:
- DTO 在 Infrastructure Boundary 结束,不进入 Presentation;
- Component Render ViewModel、发送 Intent,不拥有 Use Case;
- 新 State 必须有唯一 Ownership;
- 新 Feature 尽可能按业务 Slice 建立;
- Layer Dependency 有明确方向;
- Legacy Bridge 只允许把旧系统隔离在边界之外;
- 新 Code 不复制旧 Shared Global Pattern。
这些 Rule 的作用不是创造 Dogma。
而是让不同 Developer 的 Tactical Improvement 可以兼容。
如果每个人都朝不同“更好方向”重构,最后仍然不会出现共同 Architecture。
新 Feature 沿目标结构实现——前提是真有 Boundary
Section titled “新 Feature 沿目标结构实现——前提是真有 Boundary”当新 Requirement 与一个相对清晰 Capability 对齐时,它可以成为 Reconstruction 的自然入口。
比如旧系统里的 Customer Edit 分散在多个 Component、Global Service 和 Shared State 中,而新 Requirement 正好要求一个完整 Edit Flow。
Team 可以选择继续向旧 Service 添加 Method。
也可以定义一个新的 Customer-Edit Slice:明确 Read Model、Command、Validation、API Adapter 和 ViewModel,把旧系统通过窄 Bridge 接入。
这样 Product 获得 Feature,Architecture 同时获得一个新的可用 Boundary。
但这条路有一个重要前提:Boundary 必须来自业务 Responsibility。
不能因为 Team 最近学了 DDD,就把每个 Screen 人工包装成 Bounded Context。
Tactical Reconstruction 应该减少真实 Dependency,而不是增加 Pattern Vocabulary。
Situation 2:Stakeholder 明确支持 Reconstruction
Section titled “Situation 2:Stakeholder 明确支持 Reconstruction”如果 Organisation 明确承认结构问题,并提供 Budget、Priority 和决策空间,Strategy 可以更系统化。
此时应该讨论:Domain Analysis、Target Architecture、Migration Sequence、Test Strategy、Platform Investment、Data Ownership、Team Boundary、Operational Transition 和 Decommission Plan。
但“有 Mandate”仍然不等于应该 Big Bang Rewrite。
Big Ball of Mud 最大的问题之一正是:系统 Behavior 的完整范围没人真正知道。
一次性 Rewrite 会把这个 Unknown 全部聚集到一个 Cut-over Moment。
更有价值的是让 Mandate 支持长期 Incremental Reconstruction,而不是只支持一个漂亮 Target Diagram。
Strangler Fig:不要一次承担全部 Unknown
Section titled “Strangler Fig:不要一次承担全部 Unknown”Fowler 的 Strangler Fig 是这里特别重要的思维模型。
它的价值不只在“新旧系统同时存在”。
真正价值是控制 Bet Size。
一次只选择一个明确业务 Slice,先理解现状、确认 Requirement、建立 Safety Net,然后在新结构中实现。Traffic、User Flow 或调用逐步转向新路径。新路径稳定后,旧实现被真正删除。
然后再选择下一块。

这样每一次 Migration 都可以回答具体问题:Behavior 对吗?Contract 对吗?Operational Monitoring 足够吗?Team 是否真正拥有它?旧路径是否可以删除?
Reconstruction 仍然困难。
但 Unknown 不需要同时全部被理解。
Strangler Fig 的本质不是新旧并存,而是把系统级风险拆成一连串可以验证的小赌注。
为什么我会非常谨慎对待 Big Cut-over
Section titled “为什么我会非常谨慎对待 Big Cut-over”Big Bang Rewrite 有一个特别不舒服的悖论。
越是需要 Rewrite 的系统,往往越没有人完整知道它的真实 Behavior。
但一次性 Cut-over 恰恰要求 Team 在新系统上线前重新实现所有必要 Behavior。
这会引出熟悉的问题:旧 Code 里哪些 Condition 是业务规则?哪些是 Bug?哪些 Customer 仍然依赖?某个看起来无用的 Side Effect 是否支撑另一个 Flow?
新系统如果简单复制旧 Behavior,可能把 Mud 重新编码一遍。
如果大胆“清理”,又可能删除真实 Requirement。
因此 Big Cut-over 不只是 Implementation Risk。
它首先是 Knowledge Risk。
如果 Organisation 没有可信方式回答“我们是否真的知道必须保留什么”,大规模 Rewrite 的 Target Architecture 再漂亮也无法解决这个问题。
Requirements Engineering 不是旁边的小事
Section titled “Requirements Engineering 不是旁边的小事”Legacy Reconstruction 常常被定义为“技术项目”。
但实际工作很快会变成 Requirements Recovery。
Liu、Alderson 和 Qureshi 很早就研究过如何通过 Behavior Analysis 从 Legacy System 中恢复 Requirement。
原因很简单:长期系统里的 Business Knowledge 往往部分存在于 Code 本身。
Reconstruction 因此必须重新提问:
- 这个 Behavior 今天仍然业务必需吗?
- 哪个 Stakeholder 能确认?
- 哪个 Customer 依赖它?
- 是 Policy,还是历史 Accident?
- 是 Bug,被用户学会 Workaround,还是正式 Requirement?
- 新系统应该保留,还是有意识不复制?
这些问题不能交给 Developer 通过阅读 if 自己决定。
如果未来 Behavior 没有被确认,Reconstruction 就只是把对旧 Code 的猜测搬进新 Code。
保护业务 Behavior,而不是旧实现
Section titled “保护业务 Behavior,而不是旧实现”Migration 的 Safety Net 不应该要求新系统内部继续长得像旧系统。
应该保护的是业务确认的可观察 Behavior。
如果用户在某种状态下不能执行某个动作,新系统也应该保持这一 Rule——如果业务确认它仍然有效。
但旧系统里为了实现它使用三个 Global Flag、两个 Shared Service 和一个 Event Bus,这些 Implementation Detail 不需要被保护。
Acceptance Example、Contract Test、E2E Test 的价值就在这里:它们把“我们必须保持什么”与“旧系统是怎样做到的”分开。
这给新 Architecture 真正的自由。
否则所谓 Rewrite 只会变成 Semantic Copy。
Characterization 还不是 Acceptance
Section titled “Characterization 还不是 Acceptance”Feathers 的 Characterization Test 非常有价值。
它回答:
旧系统现在实际上做什么?
这在 Legacy Work 中是重要知识,因为很多 Behavior 没有 Documentation。
但 Characterization 不自动等于 Future Requirement。
旧系统可能包含 Bug、过期 Workaround、历史 Customer Special Case、错误 Security Rule。
Acceptance Test 回答的是另一问题:
未来系统应该做什么?
两者都需要。
Characterization 帮助我们不盲目改变未知行为。
Acceptance 帮助我们决定哪些行为值得被保留。
“它一直这样工作”是 Evidence,不是自动的业务授权。
Safety Net 应该在 Reconstruction 之前建立
Section titled “Safety Net 应该在 Reconstruction 之前建立”最危险的 Migration Strategy 是:先把系统重写大半,再开始补 Test。
这意味着 Team 在 Unknown 最大的时候没有任何 Safety Net。
Legacy Reconstruction 的第一笔技术投资经常不是新 Module,而是 Evidence。
Evidence 可以来自不同层次:Unit Test、Integration Test、Characterization Test、Golden Master、Contract Test、E2E、Production Log、Monitoring、真实业务 Example。

目标不是先达到理想 Test Pyramid。
而是先获得足够 Confidence,让下一步结构改变可以被验证。
在 Big Ball of Mud 中,这种 Safety Net 可能不漂亮,甚至很粗。
但它比没有 Evidence 的“勇敢 Refactoring”专业得多。
不要按 Folder 切
Section titled “不要按 Folder 切”旧 Folder Structure 往往正是失效 Architecture 的一部分。
如果旧系统有 components/、services/、models/、utils/,把这些 Folder 一一迁到新 Repo,并不会产生新 Boundary。
它只是复制技术 Layer。
更有价值的是寻找 Vertical Capability:一个用户 Intent 如何从 UI 进入 State / Application,调用什么 API,拥有哪类 Data,产生什么 Result。
比如 Order Cancellation、Customer Edit、Invoice Approval。
每个 Slice 应该尽可能拥有完整业务 Responsibility,而不是只拥有“所有 Component”或“所有 HTTP Service”。
不要沿旧文件夹拆分 Legacy。沿未来真正希望独立变化的业务能力切。
这也是为什么 Domain Analysis 与 Team Ownership 会在 Reconstruction 中如此重要。
Reconstruction 是一连串小 Product Decision
Section titled “Reconstruction 是一连串小 Product Decision”每迁一个 Slice,都不仅是技术 Work Package。
它会重新决定:旧 Behavior 保留什么?Boundary 在哪里?Data Ownership 是什么?Public API 是什么?谁负责 Operation?旧 Path 何时关闭?
这些都是 Product + Architecture Decision。
因此 Reconstruction 不能在开头做一次 Architecture Workshop,然后把后续工作当机械 Migration。
Team 会不断从真实 Slice 中学到新的 Domain Information。
Target Architecture 也应该允许基于新 Evidence 调整。
Incremental Reconstruction 的优势之一,就是这种 Learning 发生在 Bet 仍然小的时候。
旧 Path 最终必须消失
Section titled “旧 Path 最终必须消失”很多 Migration 不是失败在新系统没建出来。
而是失败在旧系统从未真正退役。
新 Slice 已上线,但旧 Endpoint “以防万一”保留。两边继续接受 Change。Data 被双写。某些 Customer 走旧路,某些走新路。两套 Team 都需要了解两套系统。
Transition Architecture 本来应该是临时成本,最后却成为永久架构。
所以每个 Strangler Step 都需要 Decommission Condition:什么时候可以证明旧 Consumer 已迁移?哪个 Metric 表明新 Flow 稳定?谁拥有关闭旧代码的 Decision?
Jørgensen、Nereng 和 Torrissen 对 IT System Decommissioning 的调查也提醒:停用系统本身是独立工作,不会因为“新系统已经上线”自动发生。
Migration 没有完成,直到旧 Path 真正消失。
“不能顺手做”真正是什么意思
Section titled ““不能顺手做”真正是什么意思”说 Big Ball of Mud 不能“顺手解决”,并不意味着所有 Architecture Work 都必须成为独立两年 Programme。
真正意思是:Organisation 必须承认 Reconstruction 消耗 Capacity,而且这种 Capacity 不能永远假装免费。
有时可以持续投入 20%。
有时一个真实 Feature 本身就能同时创造 Business Value 和新 Boundary。
有时 Mandatory Upgrade 可以成为抽离 Legacy Dependency 的窗口。
形式可以 Tactical。
关键是 Effort 必须被看见、被允许,并且 Direction 一致。
如果 Organisation 每次都只接受当下最便宜的 Implementation,而且任何额外结构工作都必须藏在 Developer 的私人时间里,那么再好的 Guideline 也无法长期抵抗 System Incentive。
如果 Organisation 不配合呢?
Section titled “如果 Organisation 不配合呢?”这时需要一个诚实的 Responsibility Boundary。
个人和 Team 仍然可以做很多:不再增加新的坏 Dependency;在局部建立 Test;记录 Risk;提出更小 Proposal;用数据展示 Change Cost;让新 Feature 更清楚;帮助其他人建立 Knowledge。
这些行动并不小。
多年下来,它们真的可以形成新的稳定区域。
但这种空间存在上限。
没有任何 Refactoring Trick 能让一个 Development Team 永久补偿缺失的 Organisational Mandate。
如果结构工作原则上不被允许、任何额外交付时间都不可接受、每个 Change 都只能按短期最便宜路径评价,那么技术可实现的改善范围就是有限的。
这不是在评价 Organisation 道德上错误。
它的 Priority 也许经济上完全合理。
只是要诊断在这种条件下什么技术结果是真实现实的。
一个人无法替代 Mandate。
这也回到最开始的问题。
局部 Refactoring、Fowler 与 Feathers 的技术,仍然是长期系统的重要工具。
但成熟 Big Ball of Mud 达到一定规模和系统性 Entanglement 后,很多彼此独立的小改善不会自动重新聚合成一致 Architecture。
这时需要沿业务 Slice 逐步 Reconstruction,需要被业务确认的 Future Behavior,需要 Acceptance / E2E Safety Net,需要共同 Architecture Guideline,让不同 Developer 构建的是同一个 Future Direction,也需要 Organisation 在实际条件允许范围内提供时间和 Legitimacy。
Strangler Fig 为此提供了特别有价值的模型:不要一次承担全部 Unknown,而是理解一个 Slice、业务确认、建立 Safety Net、重建、把 Responsibility 和 Traffic 转向新 Path、删除旧 Path,然后才开始下一次 Bet。
这不会让 Modernization 变简单。
但它能限制同时必须被理解和控制的范围。
于是整个论证回到开头:
Big Ball of Mud 不应该由一个人单独拯救。
这并不是因为一个人一定不够聪明、不够努力。
也许系统里确实存在一个人,他最懂 Legacy、最快发现 Boundary,也是 Reconstruction 最重要的起点。
真正危险的是,未来系统再次只依赖同一个异常优秀的人。
如果新的 Architecture 只有当某个人把全部关系放在脑中时才成立,那么它已经复制了自己试图摆脱的问题。
新 Architecture 不能只存在于最懂旧 Big Ball of Mud 的那个人脑中。
走出去不是 Hero Story。
它是一场共同承担的 Reconstruction。
- Feathers, Michael C. (2004): Working Effectively with Legacy Code. Prentice Hall / Pearson, ISBN 978-0-13-117705-5. 书中讨论 Characterization Test、Seam Model 以及如何受控修改难以测试的 Legacy System。Pearson
- Fowler, Martin (2004): Original Strangler Fig Application. 对逐步替代、而不是一次 Big Cut-over 的早期描述。martinfowler.com
- Fowler, Martin (2024): Strangler Fig. 对 Strangler-Fig 原则在 Incremental Legacy Modernization 中的更新说明。martinfowler.com
- Fowler, Martin (2018): Refactoring: Improving the Design of Existing Code, 2nd ed. Addison-Wesley. 另见 Definition of Refactoring.
- Jørgensen, Magne; Nereng, Johan; Torrissen, Tobias (2026): A survey of the decommissioning of IT systems. Journal of Systems and Software, 241, 113016. DOI: 10.1016/j.jss.2026.113016.
- Liu, Kecheng; Alderson, Albert; Qureshi, Zubair (1999): Requirements recovery from legacy systems by analysing and modelling behaviour. Proceedings of the International Conference on Software Maintenance, 3–12. DOI: 10.1109/ICSM.1999.792485.