大型遗留系统架构重构的命门

焦油坑

遗留系统重构是个老生常谈的问题,因为在一个腐化甚至大泥球的架构上又脏又快的开发(这里的快是指代码写得快,而不是指更高质量端到端交付的快)是一件熟悉的机械的工作,处于一个稳定的逐步下滑的状态,特别在新增功能和故障双爆发double-killing的时候,项目从上到下都会陷入死亡行军中。犹如掉入一个焦油坑,越是挣扎滑落越深。一般没有外力推动的情况下,根本无法自拔。

麻木

更加关键的是,死亡行军中的人虽然很痛苦,但是并没有太多的心里的不安,认知让他们觉得软件项目就该如此。就像一个驼背,没有治好之前,不会感受到直着身子呼吸的畅爽。即使痛苦,也是麻木很久了。毕竟每天都能看到功能交付的进展,痛就痛吧。

欲速则不达

而对架构腐化的修复的收益是长效的,人普遍不能接受延迟满足的等待,因为延迟满足就是个反人性的过程,在巨大交付压力面前,如果没有老司机引路,大家一般都会选择看起来速效的又脏又快的解决方案,结果往往是越是抛开架构的裸奔,越是会使你的交付变慢。

上图中架构相当于地基、车轮和动力构成的一个系统,车上压的货物越多,车轮陷得越深,动力越是不足。所谓欲速则不达,货物运送的越慢取决于整个系统,而不是看着拉得多,其实单趟时间加长,整体效率反倒是降低的。

重构误区

因此,对于架构的重构和维护就变得非常重要。对于重构,往往会陷入两个误区:

– 过于保守

害怕风险,不愿意重构;或者总是想找到一个合适时机,重构一拖再拖。

– 过于激进

重构采用休克疗法蛮干,整个系统全部推到重写,由于缺乏对老系统的有效继承,新老系统功能对齐工作量巨大,新系统迟迟不能上线;而且往往缺乏防护网守护,风险巨大。

架构重构的命门

重构一定要有正确方法论的指导。否则很可能要么达不到重构目标,要么又很快再次陷入焦油坑,等于没重构。

遗留架构重构中靠谱的重构路径非常重要。

一旦要路径,就必须有现状和目标。同时沿着路径从现状到目标的过程中,为了防止出现偏差,相应的标尺就变得十分重要。

这样大型遗留系统重构路径的命门就全了:

标尺-》现状-》路径-》目标

标尺

重构的过程就像一个砌墙,砌墙一般是铅锤来标定和度量,不然墙很容易歪掉。这里铅锤就是一个标尺。

重构也一样,没有规矩不成方圆,只有设定好标尺,才能做好重构。

架构度量

好的架构,核心特征之一就是高内聚、低耦合。

展开架构度量之前,我们先看和耦合相关的几个概念。

(~)波浪线表示高能,高能部分可以跳过不影响阅读。

~~~~~~~~~~~~~~~~~begin~~~~~~~~~~~~~~

图片

不稳定度

计算公式:代码原子出向依赖数量 / (入向依赖数量 + 出向依赖数量)

要求依赖必须要指向更稳定的方向。这里的不稳定度指的是组件/模块的变更成本,和它变更的频繁度没有直接的关联

连通度

计算公式:100*n/(V(V-1)),计算方法见下面例子。

这个指标用来描述组件内原子间的依赖程度,指标值越大,分数越低。

指标值在2%和5%算正常,高于5%的需要优先改进

公式中的n是系统依赖图中从a可到达b的对(a,b)的数目(依赖图中传递闭包中的边数)

比如下面这个由ABCD四个原子组成的组件的连通对为(B,A),(C,A),(B,D),(C,D),(A,D),所以n=5,

连通度=100*5/(4*3)=41.67

平均依赖

计算公式:E/V

该指标主要用来描述组件内平均每个原子的依赖数,指标值越大,分数越低,建议指标值在5以下。

(下图中的V)间的依赖关系(下图中的E)

循环依赖占比

计算公式:100 * n / (V*(V-1)/2)

度量系统依赖图中强连接的系统对的百分比。如果a可从b到达并且b可从a到达,则a和b是强连接。

耦合计算为100*n/(V*(V-1)/2),其中n是强连接对的数量。指标值越大,分数越低,建议指标值为0。

下图右上角内容即为反向依赖

以上指标如果条件允许,可以通过架构度量工具(caa、lattix、s101等)获取。

~~~~~~~~~~~~~end~~~~~~~~~~~~~~

架构重构的方向很重要的一个方面就是解耦,解耦的效果可以选取以上耦合度指标作为架构架构度量标尺,可视化架构重构中解耦的效果,可以有效防止重构过程中架构的腐化。

重要

架构优秀和架构度量指标之间是一个充分不必要的关系(就像体检一样,身体好体检指标会正常,体检指标正常身体可不一定好),既架构优秀,架构度量指标一定好,但是指标好,架构却不一定优秀。

优秀架构一定是高内聚低耦合的,架构度量基本都是反映架构耦合程度的,虽然降低耦合可以提升内聚,但不是完全对应的,所以架构耦合度只能作为架构优秀的部分条件。

防护网

架构重构要想方设法充分利用已有系统的自动化ST、FT作为防护网,并进一步补充这些已有防护网,同时架构重构过程中要抽取组件/模块,过程中要采用TDD的方式开发,补充尽量端到端的黑盒UT用例,完善测试金字塔,提升自动化测试的防护的效率。

重构的过程本身就是解耦的过程,其中防护网建设更是重中之重的工作,必须投重兵充分开展。

实际案例

我们通过一个实际案例来展开现状、目标和路径这些命门的应用。一个实际的遗留系统:

遗留系统情况

代码历史接近 10年

开发人员超过 30+

c/c++代码 200W+

全量构建 30分钟以上

核心痛点

系统耦合大

重复代码多

问题

项目经理说:一些简单的功能发布都要一个月以上

用服人员说:全面的熬夜升级值守都在这个项目,而且熬夜熬的提心吊胆。

开发人员说:这个代码,读不太懂不敢改 …

测试人员说:质量太差,故障泄露防不胜防…

现状

通过架构度量指标给出现状,架构质量非常差。

耦合度指标

重复度指标

目标

重构过程中,如果缺乏重构目标,很容易掉入以下几个陷阱:

1、重视新老功能对齐、轻视架构目标,重构完成后,架构依旧不优秀,重构效果打折扣。

a、完全抛开老系统,重写一套新的架构,以对齐老功能为目标,然后不停追赶重构过程中的新功能和故障单。由于交付压力巨大,陷入功能风暴和故障风暴中双杀,架构迅速腐化,再次掉入焦油坑。

2、架构目标不明确

a、重构没有目标约束,做到哪里算哪里,甚至连自身团队的潜能都没能发挥出来,重构意义不大。

这里给出本次架构重构抽象目标

– 解耦、消重

– 建立标尺(架构度量+防护网)

– 保持随时发布的能力

具体目标设定我们结合例子详细展开。

路径

路径是指从现状到目标之间的通道,只有路径清晰,才能做到有条不紊,才能给项目关键干系人(领导、项目成员)信心,才能推动架构重构开展和获得持续支持。

现状

目标

路径

大型遗留系统重构路径

对于大型遗留架构来说,考虑版本随时发布和老架构继承利旧,架构重构路径建议为:

1、新老并存,通过脚手架的方式实现新老功能路由和新老系统互通,这样可以保证目前交付同时最大限度继承老系统的已有实现。

2、考虑重构的优先级,从现状到目标给出明确方向和该方向上的重构组件/模块。

3、老系统逐步萎缩,新系统逐渐壮大,功能全部移植完成后,拆除脚手架,此间版本随时可以发布。

案例重构过程

重构过程如下:

分析现有架构问题,梳理出现状

关键实践:

通过架构依赖分析工具分析目前架构耦合情况

设计期望架构,给出目标

分层规则

1、上下分层

2、层内分布

架构 Hierarchy:层->组件/模块->包/文件夹->类/文件

边界规则

现有的类/文件,包/文件夹,组件/模块出现在期望的位置。

依赖规则

1、下层(中的类)不能依赖上层(中的类)

2、同一层内的模块(中的类/文件)之间没有循环依赖,最好没有直接依赖

3、各层都不能直接依赖infrastructure(基础设施)层,必须通过抽象层依赖。

目标架构

关键实践:DDD

用遗留代码重构思路,明确路径

1、利用绞杀者+脚手架模式进行解耦重构,复用老系统FT/ST,补充解耦后模块/组件自动化测试用例(最好是端到端黑盒UT),完善防护网

关键实践:解耦设计、重构、TDD

2、增加架构守护、“增量”解耦出更多模块/组件

关键实践:架构度量

3、单个模块/组件解耦思路

补充

重构小组构成

重构的过程中要有组织的支持,重构小组必须有架构师、骨干员工(对原系统深入了解并经验丰富)和的防火队员(对原系统深入了解并经验丰富)组成。

持续守护

架构重构过程中或重构完成后,都需要持续的架构度量和架构review进行架构守护,防止架构再次腐化。

总结

大型遗留系统架构重构首先要摸清现状,其次是树立目标,并建立标杆,然后设定路径,中间辅助数字化展示纠偏。只有有了清晰和可达的目标和路径,才能为整个团队指明重构方向,树立信心,并获得项目关键干系人的支持,重构才能获得成功。

祝大家重构顺利。

阅读原文


作者简介: 代码匠艺,软件系统架构,AI平台和应用,生活趣事。欢迎关注微信公众号:丁辉的软件架构说

声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/213239.html

联系我们
联系我们
分享本页
返回顶部