大家好,我是Z哥。
这是我的第199篇原创,离200篇还有一步之遥。为了庆祝这重要时刻,我也在琢磨着要不要在第200篇的时候搞个什么小活动。还没想好,等下周再看吧。
不过不管怎样,你也应该为Z哥的坚持点个赞吧,哈哈。所以,看完文章后不要忘了划到文末点赞哦~
好了,回到正题。
很多人吐槽老项目是怎么怎么垃圾。这的确是一个很大的问题。根据 IEEE Spectrum 之前发布的一份报告数据:
自 2010 年以来,全世界IT 产品和服务支出约为 35 万亿美元,其中约四分之三用于运营和维护现有的 IT 系统。另外,至少有 2.5 万亿美元用于尝试替换旧的 IT 系统,而且其中约有 7200 亿美元被浪费在失败的替换工作上。
https://spectrum.ieee.org/computing/it/inside-hidden-world-legacy-it-systems
因此,面对不堪的、充满历史味道的老项目对程序员群体来说会是一个常态。
不过我们可以做些什么呢?难道是要么硬着头皮去填别人挖的坑,要么跑路么?
如果你不跑路的话,最常见的应对方式无非是「重写」或者「重构」。
一 重写
一般来说,越是年轻的程序员越是最喜欢重写。除了年轻气盛的原因外,主要是因为完全摆脱了老代码的技术债和束缚,可以尽情地施展自己的才华。
但是对于老系统的重写,如果没有高层的支持、业务部门的配合,靠谱的开发团队,这事大概率很难能按预期完成。
而且选择「重写」方案不仅仅是重新写一套代码这么简单,系统切换也是一个头疼的事情,在复杂一些的项目里,它的难度并不亚于重写一套代码。
而且一旦选择「重写」,意味着在一段时间内要么老系统并行开发,要么会攒一堆需求,业务方能不能持续支持「重写」也会面临很大的挑战。
就算这些问题都能搞定。「重写」还可能造成的一个结果是:在过了几个月后,重写后的系统又成了别人眼中的“破烂不堪的老系统”。这是最讽刺的……
如果在重写过程中,由于任何原因牺牲了对质量的要求,这个讽刺很容易成为现实。
因此,很多公司之所以不支持重写,理由是:
又不是不能用。
这么看来他们并不是迂腐,也是一种理性的选择,也是没错的。
二 重构
相对地,年纪越大的程序员可能更倾向于重构。毕竟他们有更多的机会见过、经历过那些惨痛的重写事件。当然更主要的原因是,他们掌握了更多应对“垃圾代码”的方式方法,认为通过小范围的代码重构也能化腐朽为神奇,提升项目质量。
的确没错,但这是有一个前提的。就是垃圾代码的产生速度要低于重构的速度。可是对大部分中小公司来说,开发团队可能不具备这样的条件。因为技术好的程序员要么走向了管理岗位,要么身上的重担太多,没太多时间来做重构。再加上技术差的程序员继续复制粘贴,挖更多的坑。通过重构来改善老项目,可能永远在“重构的路上”。
而且,如果项目的技术复杂度大于业务复杂度,那么配合重构的测试成本就很高了。
除了这两种常见的方式外,还有其它的方式也能应对这些老系统。它们的的风险和收益在下图里已经很好地呈现出来了。
其实Z哥认为,这些方式我们都可以用,但是要沿着正确的思路来。(绞杀是什么?往下看)
首先从业务角度来考虑到底是选择哪种方案。
- 从业务敏捷视角来看,能不能让响应力变得更快?
- 从运营效率视角来看,如何通过系统改造,提升业务运营的效率?
- 从客户洞见视角来看,如何让系统更好地发现客户洞见,进而更好地理解客户需求和演进产品?
其次是从系统本身来考量,比如,我们想通过改造获得多大的弹性?
最后,根据整体上对业务帮助的价值大小来选择哪一种方式。如果对业务的帮助巨大,完全值得投入大量资源去重写,那么就不要畏畏缩缩的小范围重构。如果对业务帮助很小,那么也就别想着重写了,老老实实在小范围内修修补补就好了。
当你拿捏不准方案的时候,选择按模块替换会是一个不错的办法,相当于通过每一次换一个零部件,把整个机器上的零部件全部翻新一遍,以此完成重写的效果。Martin Fowler 称之为「绞杀法」。
「绞杀法」的最佳实践是DDD+微服务,因为它们可以提供更好的「隔离性」「自治性」,更有利于「替换」的进行。
当确定好改造的方案后,还需要制定度量指标,在实施改造的过程中持续关注这些指标的变化。最好能够通过可视化工具将这些信息共享出来,这样的好处有两点,
- 一是能够让团队了解改造进展和成果,确保改造朝着正确的方向走;
- 二是能够让相关的干系人(领导、业务部门)也能了解到工作的进展情况,提高对预期的确定性,以持续获得他们的支持。
关于具体重构方法我就不说了,可以翻看我之前的一篇文章《好的重构方法才能摆脱“屎山”》。
关于重写可以来聊几句,之前我们没聊过。我的建议是遵守以下几个原则。
系统具备演进的能力。
凭借度量指标来把握演进的方向。
小步快速迭代。
01 系统具备演进的能力
既然是重写系统,那么一定要为可见的未来做一些预留,方便后续的演进。毕竟能够决定重写的系统自然是比较核心的系统,不管在技术上还是在业务上都会持续发生变化,注重系统的演进能力就是在降低未来项目复杂度增长的幅度。
具体的方法可以概括为3个词:抽象、分类(分层)、解耦。
02 凭借度量指标来把握演进的方向
前面也提到了度量指标的价值。这里继续强调一下它的重要性。
如果说在重写时期,度量指标是衡量重写工作完成好坏的尺子,那么在重写后它就是指引未来系统演进的明灯。
不管系统后续要做什么升级改造,对自身了解的越清楚,做出的决定和选择自然越合理。而度量指标起到的就是这个效果。
一个缺少度量指标的系统,随着业务逻辑的不断堆砌,很容易自由生长过了头。
03 小步快速迭代
近些年随着CI/CD的普及,小步快跑式的敏捷开发被越来越多人提到,并且开始运用。
它的好处是显而易见的,对项目的可控性更高,容错性更强。
如果你不知道“小步”该多“小”?你就按发布出了问题能不能快速回滚作为标准去考虑就好了。能快速回滚的迭代节奏就是“小步”。
其实重写系统完之后还有一个头疼的事情要解决,就是数据迁移。业界常用的方案有四种,我先大致列一下主要思路,后续再发文展开聊聊。
01 双写
- 新库配置为旧库的从库,从旧库同步数据。
- 数据写入的时候,不仅要写入旧库,也要写入新库。
- 数据校验。
- 两边数据完全同步后,灰度切流量。
由于有双写的存在,所以在切换的过程中出现任何的问题,都可以将读写流量随时切换到旧库去,安全感极强。
02 异步双写+对账
这个方案其实是双写的变种,将同步写入变成了异步写入。也因此需要一个对账机制,确保最终一致性的达成。
一般这个异步的机制要么通过 MQ ,要么通过数据库的 binlog 进行。
03 直接用新库,数据惰性迁移
这个方案中间需要架设一个缓存层,用来存放数据的key。过程是这样的:
- 先访问新库,如果有数据则直接进入步骤 3 ,如无数据进入步骤 2 。
- 访问旧库,找到相应的数据 Insert 到新库,再进入步骤 3 。
- 进行业务逻辑的操作……
随着时间的推移,数据慢慢会从旧库同步到新库。剩下还未同步的数据使用同步工具做一次全量同步。
原则上,以上的每一个方案在实际切流量之前都要对数据做核对,确保两边的数据一致。
最后迁移完后可以通过流量回放工具(如阿里的 jvm-sandbox-repeater )或者全链路压测工具来验证新版本的系统是否能运行地符合预期。
好了,总结一下。
这篇呢Z哥和你分享了应对老项目的思路,总的有5种:
- 重构
- 重新部署
- 更换平台
- 重写
- 绞杀
我建议根据业务敏捷、运营效率、客户洞见、系统本身的韧性和弹性来考虑选择哪一种。
接下来专门对重写展开细说了一下,建议遵守三个原则:
- 系统具备演进的能力。
- 凭借度量指标来把握演进的方向。
- 小步快速迭代。
最后分享了3个常用的新老数据迁移方案。
- 双写
- 异步双写+对账
- 直接用新库,数据惰性迁移