软件架构如同生命体,有其诞生、成长、壮年与衰老。架构老化是必然的——随着需求不断叠加,最初精心设计的架构逐渐变得臃肿、脆弱、难以维护。许式伟没有回避这个残酷现实,而是直面它:架构老化不可完全避免,但可以通过正确的重构策略来延缓老化、恢复活力。
重构不是推倒重来。推倒重来是承认失败,而重构是在既有基础上焕发新生。两者的选择,取决于老化的程度与重构的能力。
架构老化的根本原因不是技术选型错误,不是工程师能力不足,而是需求的不确定性与架构的确定性之间的永恒矛盾:
要点解读:架构老化的症状分布在代码、模块、系统、团队四个层面,从代码臭味、接口腐化一路演化到核心系统伤害值升高乃至团队陷入重构恐惧。识别这些由浅入深的信号,是判断何时、以何种力度介入重构的前提。
基于第58讲的架构质量评估框架,架构老化可以用以下量化指标衡量:
| 指标 | 计算方式 | 健康阈值 | 老化预警 |
|---|---|---|---|
| 核心系统伤害值 | $\sum \log_2(\text{修改行数}+1)$ | 低 | 持续增长 |
| 模块总耦合度 | $\sum C_A \times \text{不成熟度系数}_A$ | 低 | 依赖数量增加 |
| 新功能开发成本 | 人天/功能点 | 稳定或下降 | 持续上升 |
| 缺陷密度 | Bug数/千行代码 | 低 | 持续上升 |
| 测试覆盖率变化 | 新增代码覆盖率 | 不低于既有水平 | 持续下降 |
要点解读:图左的恶性循环中,需求叠加导致核心系统膨胀,最终使测试困难、缺陷增多,又反过来加剧补丁式开发;图右的重构良性循环则通过审视边界、正交分解实现核心系统瘦身并提升效率。打破两者的联结点,在于以重构主动切入老化的起点,而非等待腐化累积到被迫重写。
核心思想:在正常功能开发过程中持续进行小规模重构,不让问题积累。
适用条件:架构老化处于早期,核心结构仍然健康,只是局部模块需要优化。
实施要点:
核心思想:对特定模块进行独立的架构调整,不影响其他模块。
适用条件:某个模块的接口仍然合理,但实现已经腐化;或者模块的接口需要调整,但影响范围可控。
实施要点:
核心思想:对跨多个模块的子系统进行重新设计。
适用条件:模块边界需要重新划分,如《架构分解与全局性功能设计》中 IO 子系统从 Visitor 模式迁移到 IO DOM 模式。
实施要点:
核心思想:对核心系统进行重新设计,影响面最大。
适用条件:核心系统伤害值极高,新功能开发几乎无法进行。
实施要点:
要点解读:重构策略的选择取决于老化程度与接口的健康状况——早期优先持续小步重构,模块腐化时视接口是否合理选择替换实现或调整接口,子系统与核心系统腐化时才启用更大范围的重构,其中核心系统重构是影响面最大的最后手段。
许式伟强调:改代码的过程才是架构能力真正升华的过程。 推倒重来意味着放弃了从既有架构中学习的机会,也放弃了在约束中创新的能力。
满足以下全部条件时,可以考虑推倒重来:
要点解读:推倒重来往往陷入低估复杂度、功能缺失、双系统并行维护、进度压力导致的连串陷阱,最终新系统同样走向老化。它只能作为既有架构完全过时、理解成本过高时的最后选择,而非面对老化的默认答案。
| 原则 | 说明 | 权衡 |
|---|---|---|
| 测试先行 | 先建立测试保障再重构 | 测试编写成本 vs 重构安全性 |
| 小步迭代 | 每步可验证、可回滚 | 迭代成本 vs 风险控制 |
| 保持运行 | 系统始终可用 | 过渡期复杂度 vs 业务连续性 |
| 接口稳定 | 优先保持接口不变 | 接口约束 vs 重构灵活性 |
| 同理心 | 理解既有设计再改动(参见第57讲) | 理解成本 vs 改动质量 |
核心权衡:重构投入 vs 业务交付。
架构师面临的永恒张力是:业务方要求快速交付新功能,而重构需要额外时间。许式伟的建议是:早迭代、小步迭代——不要等到架构老化到无法挽回时才重构,在正常开发过程中持续小规模重构。这既是技术策略,也是心性修炼(参见第57讲)。
《架构分解与全局性功能设计》详细描述的 IO 子系统架构演进,就是一次成功的子系统级重构:
用新的微服务逐步替换单体应用中的模块:
Netscape Navigator 6.0 的推倒重来是软件史上最著名的失败案例之一:
"我们下个版本做一次大重构"——这是最危险的重构方式:
参见第57讲「心性:架构师的修炼之道」(详见架构思维/架构设计篇对应章节)——重构中的同理心与迭代能力。参见第58讲「如何判断架构设计的优劣」(即本系列《架构设计的评判与业务导向》)——架构老化的量化评估。参见第60讲「架构分解:边界,不断重新审视边界」(详见架构思维/架构设计篇对应章节)——IO 子系统的架构演进案例。