本篇要回答的问题:架构为什么会老化?在不轻易动核心系统的前提下,老系统怎么加新功能、怎么做局部优化,又在什么时候、怎样才真正去重构核心系统?
上一讲讲文本处理范式的日常积累。本讲转向另一种日常功夫——应对架构老化。“架构的功夫全在平常”,能力提升靠一点一滴的反思打磨,不能指望突飞猛进。本讲给出一条由轻到重的路径:加新功能 → 局部优化 → 核心系统重构。
架构为什么老化
老化源于:不断加新功能时,功能实现方式逸出当初框架范围,代码散落各处,把系统绞得支离破碎。
老化的标志:添加功能越来越难、迭代效率降低、问题持续不断,解决一个问题又生出好几个新问题。
理想情况下坚持"最小化核心系统 + 多个正交周边系统"就很难老化,但现实中难以避免,常见原因:
| 原因 | 说明 |
|---|---|
| 工程师能力不足 | 以功能完成为先,不顾长期维护成本 |
| 缺乏架构评审 | 代码质量缺乏持续有效关注 |
| 需求理解不深 | 最初架构无法满足迭代发展 |
| 架构迭代不及时 | 大量赶时间的补丁式代码 |
应对老化分两个视角,先做日常高频的"局部",再谈系统性的"重构"。
老系统怎么添加新功能
把新功能定位为周边功能,少给核心系统添麻烦、能少改就少改。但这还不够——周边系统本身也应被看作独立业务系统,要尽量不依赖既有系统。
关键判断(三模块拆法):任何周边系统 B 与核心系统 A,理想结果是三个模块——A、B(与 A 无关部分)、A 与 B 的桥接代码(与 A 相关部分)。桥接代码虽归属 B,但应尽可能小、且尽可能独立于与核心无关的代码。这样才能保护投资:今天的投入最大保留,未来重构成本也最小。
要不要依赖公司内部基础库?辩证看:成熟度越高越值得依赖,先评估规格成熟度(实现问题靠时间解决),看规格是否符合预期、经过多少用户打磨。
实战范例(电子表格智能填充),几百万行老代码上加新功能:
| 步骤 | 做法 |
|---|---|
| ① 纯算法模块 | 输入值矩阵 + 预测个数 → 输出预测矩阵;填充方向在算法中消失(按填充方向构建矩阵,而非屏幕直观矩阵) |
| ② 抽象核心系统两个接口 | 取一个区域的单元格数据;设置一个单元格的值和格式 |
| ③ 对接 | Model 层包装所需接口;取区域/设单元格是通用接口,任何既有系统都易实现 |
思路:尽量与既有系统剥离,从独立业务视角实现,抽象对环境的依赖,最后用最少量对接代码串起来。
架构的局部优化
目标是优化某功能与核心系统的耦合关系。看似收效甚微,但能快速推动,日拱一卒坚持下来效果远超想象。两种做法:
| 做法 | 关注点 | 要点 / 风险 |
|---|---|---|
| 重写(局部重构) | 功能本身的实现 | 彻底移除相关代码重写一份;必须对业务足够了解(如维护过一阵);必须清理干净老代码,不留残余 |
| 依赖优化 | 功能与系统的关系 | 做"代码搬运工":把散落代码集中,每处修改变成函数 doXXX_yyyy(XXX=功能代号,yyyy=语义命名) |
doXXX_yyyy 这个"丑名字"是故意的——作为团队约定,代表"此处待重新考虑边界"(重新考虑的是核心系统的边界,不是当前功能的边界)。
关键判断:如果某处有好几个功能都加了
doXXX_yyyy,就意味着这里需要一个事件机制让它们监听;一旦做了,核心系统就更稳定、不再因加功能而改代码——这正是 OCP 所追求的。
集中代码后,有多少个 doXXX_yyyy,就有多少对系统的"伤害"(参 58 讲伤害值计算):
| 伤害值 | 判断 |
|---|---|
| 不大 | 耦合在合理范围,到此为止可接受 |
| 过多 | 站在该功能业务视角看依赖合理性,不合理就推动局部重构 |
局部重构不应盲目,应依赖基于"伤害值"的客观判断。习惯于不理解就重构很不好——认同他人是重要的能力修炼,事情优先级排列对架构师是第一位的。
核心系统的重构
积弊已久的系统整体重构非常艰难。一上来就重构核心系统风险太高:牵一发动全身、无法保证交付周期,且没人对全局足够了解、过于盲目。正确顺序是先把周边理顺,最后动核心。
确定要重构核心系统,最高优先级是确定它的边界(使用界面/接口)。周边对核心的依赖无非两类:① 核心的功能(DOM 接口);② 核心提供的事件。步骤:
| 步骤 | 内容 | 难度 |
|---|---|---|
| ① 整理事件集合 | 对所有周边做依赖优化,初步确定核心需暴露的事件 | 中 |
| ② 实现依赖 → 接口依赖 | 把周边对核心的依赖全调整为依赖接口(不搬代码,把周边独立出去);此时即便接口不合理,也代表了当前模块边界,理论上可 mock 核心对接 | 中 |
| ③ 重新设计 DOM 接口 | 最重要时刻,审视并精炼接口 | 高 |
第③步两个难点:
- 业务理解要有长足进步:新接口要更精炼、更贴近业务本质,而非换汤不换药,否则要质疑重构必要性。
- 对周边切换成本有充足预计:从老接口过渡到新接口;虽可让核心同时维护两套 DOM 接口,但过渡期不能太长,否则让人困惑。
完成接口改造后,核心系统与每个周边系统彻底独立、可单独调优。这时"嫌核心太糟就重写"也很轻松——因为要做的事很收敛、不影响大局。而那些边界分解不清的系统,改核心系统的代码?不要命了么?
总结
应对架构老化要"功夫在平常",按由轻到重三步走:加新功能(拆成 A / B / 桥接三模块)→ 局部优化(重写 or 依赖优化,用 doXXX_yyyy + 伤害值客观判断)→ 核心系统重构(先定边界与事件、再实现转接口、最后精炼 DOM 接口)。重构比做全新架构更难,是架构设计 + 资源调度 + 阶段规划 + 韧性毅力的庞大工程。
先把"是什么"回答清楚
| 概念 | 一句话说明 |
|---|---|
| 架构老化 | 功能逸出框架、代码散落,导致加功能越来越难 |
| 三模块拆法 | A、B(无关部分)、A-B 桥接代码,桥接要小要独立 |
| 依赖优化 | 把散落代码集中成 doXXX_yyyy 函数,理清耦合 |
| 伤害值 | ∑log2(修改行数+1),处数越多伤害越大 |
| 核心重构 | 先定边界/事件,再实现转接口,最后精炼 DOM 接口 |
一句话速记
应对老化不靠一次性大重构,而靠平常:加功能拆成"核心+周边+小桥接",局部优化用
doXXX_yyyy集中代码量化伤害值,等周边都理顺了再精炼核心接口——重构要润物细无声。
几条值得记住的判断
- 桥接代码要尽可能小且独立,这是保护投资、降低未来重构成本的关键。
- 同处多个
doXXX_yyyy= 需要一个事件机制,这正是 OCP。 - 重构不能盲目,要靠伤害值客观判断;认同他人、排好优先级是架构师本分。
思考题
打开你手上一个"老化"的模块:把它对核心系统的每处侵入都改写成 doXXX_yyyy 形式后数一数,伤害值是多少?其中是否存在多个功能挤在同一处、其实在呼唤一个事件机制的信号?
