进化论告诉我们,当环境持续变化的时候,唯有不断调整自己以适应新环境的生物方能生存下去。
同样的,为了让软件架构拥有持续的生命力,我们需要主动让其演进以适应软件环境的变化。
一、架构腐化的表现
我们经常会听到这样的故事:
一开始他们进展很快,但如今,想要添加一个新功能需要的时间就要长得多了。
需要花越来越多的时间去考虑如何把新功能塞进现有的代码库,不断蹦出来的 bug 修复起来也越来越慢。
代码库看起来就像补丁摞补丁,需要细致的考古工作才能弄明白整个系统是如何工作的。
这份负担不断拖慢新增功能的速度,
到最后程序员恨不得从头开始重写整个系统。
下面这幅图可以描绘经历的困境。
随着时间的推移,架构腐化,新的功能越来越难增加,维护也变得越来越困难。
无法重构的场景
重构是在不改变代码行为的前提下,对其进行一系列小的改造,旨在改进系统结构的实践活动。
下面的场景将很难开展重构工作:
重构成本远大于重构价值
本来不复杂的业务逻辑,由于混杂了业务和技术的复杂性,代码又腐化得严重,导致系统过于复杂,而且普遍没有测试。
在这种情况下重构的成本太高。
团队没有重构能力
重构是一项需要具备很高技能的活动,遗憾的是绝大多数团队都不具备这样的能力。
- 技术好的程序员一部分走向管理岗位,一部分忙于救火。
- 技术差的程序员继续复制粘贴,挖更多的坑。
重构是一项还需要大多数程序员长时间修炼的技能。
二、可演进的软件架构
上面两种情况,建议还是放弃重构吧,可演进的软件架构就是要避免上面问题的发生,这就需要软件架构具备可演进性。
《演进式架构》这本书提到可演进性主要由三方面构成:
- 增量变化(“零敲牛皮糖”战法);
- 适应度函数(引导性变更 + 架构度量);
- 适当的耦合(架构解耦与演进);
下面是我的理解:
“零敲牛皮糖” 战法
就类似抗美援朝中,应对美军的“磁性战术”,志愿军采用了“零敲牛皮糖”战法反制,即你想要黏住我,我就干脆不走,反过来使劲一点一点地敲你,最终零零星星地把你敲光。
应对架构腐化,也应该用“零敲牛皮糖” 战法,一点点重构,持续改进,每天前进三十公里。
为保障可以持续改进(常态化增量变化),我们需要有对应的机制保障:
- 具备持续交付业务价值能力:小步提交,频繁提交,逐步放量。类似混沌工程一样,每次变更小范围,持续改进。
- 测试、灰度兜底,保障每次交付的质量;
- 自动化、流水线提效;
所有变更所依赖的基准条件都是可测试,例如:在开发层面有了测试,才可以通过测试来保障每次提交,构建出的代码在不破坏架构特征(规范)的前提下,对系统进行增量修改,在程序的构建上采用持续交付/部署流水线,可以很好的在系统执行变更时自动化的执行测试。
方向正确:引导性变更,架构度量
引导性变更可以引导相关的变更更加适应业务和技术环境的变化。
主要依赖:引导方向和度量维度的量化。
《演进式架构》这本书中的“适应度函数”,其实就是对有价值的功能性需求和非功能性需求进行度量、评估,继而确定优先级。
就类似工程监理一样,软件也应该有架构守护,确保方向正确和架构可持续演进。
- 方向性保障需要看对业务价值的大小;
- 量化需要考虑架构度量;
控制影响范围:架构解耦与演进
要能控制变更的范围,架构解耦必不可少。
下面这个紧耦合的,每次变更都是一个大动作,影响众多。
我们需要做到“高内聚,松耦合”才能更有效的控制变更:
- 高内聚,系统内的功能要素要做到高度的相似聚合,共同为一个目标服务。一个好的内聚模块应当恰好做一件事。
- 松耦合,表示两个子系统(或类)之间的关联程度,当一个子系统(或类)发生变化时对另一个子系统(或类)的影响很小,则称它们是松散耦合的。
这个转变的过程不是一蹴而就的,而是循序渐进的逐步演化的。
总结
我们在做演进式架构的时候,并不是需要启动一项庞大的改造工程,对现有的系统进行大面积的重构或者重写。
而是要围绕这个系统呈现出的一些特征,和我们整理出的对业务有价值的适应度函数,进行小步的修改,这个小步可能都不需要修改代码,仅仅是流程和部署的改变。
但是我们总是在做调整和改变,每天整体的架构都相比之前更好,这样这个系统就会逐步的处于一个可以快速演进的状态。
记住演进式架构的中心点:外部引导变得更好,内部优化,不要更差。