Day950.遗留系统的四化建设 -遗留系统现代化实战

news2025/1/11 12:58:11

遗留系统的四化建设

Hi,我是阿昌,今天学习记录的是关于遗留系统的四化建设的内容。

对于老旧、过时,但又十分重要、不可替代的遗留系统,是遗留系统。听之任之只会埋下隐患,真正出现问题就为时已晚了。在动手改造遗留系统之前,先要找准方向。其实相比遗留系统“治理”、“改造”,更强调的是“现代化(Modernization)”,也就是把遗留系统变为现代化的系统。这也是国际上更通用的提法。用“Legacy System Modernization”这个关键词,在 Google 上能搜到 1380 万条结果。很多团队在对遗留系统进行“改造”或者“现代化”的时候,往往会陷入一个误区,就是盲目引入各种时髦的新技术,仿佛“新”就代表着“好”,就代表着方向正确。

比如耳熟能详、近年来愈发流行的微服务架构,有些团队也不管自己的项目适不适合,上来就把一个“大泥球”式的遗留系统肢解成了几十个微服务。更有甚者,一个遗留系统拆成了几百个微服务,有些甚至一张表的“增、删、查、改”居然被拆成了四个服务。架构似乎“现代化”了,运维人员却“哭”了。

那遗留系统现代化的正确方向到底是什么呢?

遗留系统在代码、架构、测试、DevOps 方面存在诸多问题,在此基础上,将代码和测试合并(因为它们说的都是代码的质量),并引入开发团队这个维度,就得到了遗留系统现代化的四个方向:

  • 代码现代化
  • 架构现代化
  • DevOps 现代化
  • 团队结构现代化。

一、代码现代化

代码现代化顾名思义,就是把遗留系统中丑陋的“祖传”代码重构成职责清晰、结构良好的优质代码。

遗留系统中的代码是“祖传”的,是因为它和其他祖传的东西类似,都是历史悠久、且不敢轻举妄动的。而之所以不敢轻举妄动,就是因为缺乏测试,无法快速验证修改的正确性。而大多数情况下,之所以没有测试,又是因为代码写得不可测。可测试的代码和代码的测试是相互依存的,其中一个做到了,另一个也很容易做到,而如果其中一个没有做到,另一个也必然无法做到。因此代码现代化的首要任务,就是对遗留系统的代码进行安全的可测试化重构。

在正常情况下,重构应该是在充分的自动化测试的保护下进行的。但对于没有测试的代码,我们只能“硬着头皮”去做一些相对来说比较安全的重构,将代码重构成可以写测试的程度,然后再补上大量的测试,进而在有充分测试覆盖的情况下,进行更广泛更深入的重构。

下面的代码,想测试 if 的逻辑,当 Dao 的方法返回一个 null 时,这段代码会抛出一个异常。


public class EmployeeService {
  public EmployeeDto getEmployeeDto(long employeeId) {
    EmployeeDao employeeDao = new EmployeeDao();
    // 访问数据库获取一个Employee-+
    Employee employee = employeeDao.getEmployeeById(employeeId);
    if (employee == null) {
      throw new EmployeeNotFoundException(employeeId);
    }
    return convertToEmployeeDto(employee);
  }
}

看到这样的代码,你可能会说,这质量还行啊,可读性不错,职责也比较清晰。的确是这样,但这样的代码却是不可测的。

因为 EmployeeDao 内部会访问数据库,从中读取出一个 Employee 对象。而这个 EmployeeDao 是在方法内通过 new 的方式直接构造的,就意味着这个方法对 EmployeeDao 的依赖是固定的,无法解耦的。

要知道在单元测试中,是不可能直接访问真实的数据库的,因此要想测试这样的方法,只能先对它进行可测试化重构,也就是先将它重构为可测试的代码。什么样的代码叫可测试的呢?比如下面这样:

public class EmployeeService {
  private EmployeeDao employeeDao;
  public EmployeeService(EmployeeDao employeeDao) {
    this.employeeDao = employeeDao;
  }

  public EmployeeDto getEmployeeDto(long employeeId) {
    Employee employee = employeeDao.getEmployeeById(employeeId);
    if (employee == null) {
      throw new EmployeeNotFoundException(employeeId);
    }
    return convertToEmployeeDto(employee);
  }
}

通过这次重构,把会访问数据库的 EmployeeDao 提取成类的私有字段,通过构造函数传入到 EmployeeService 中来,在 getEmployeeDto 方法中,就可以直接使用这个 EmployeeDao 实例,不用再去构造了。由于传入的 EmployeeDao 并不是 EmployeeService 构造的,所以后者对前者的依赖就不是固定的,是可以解耦的。

如果传入 EmployeeService 的是一个 new 出来的 EmployeeDao,那和原来的方法一样,仍然会去访问数据库;如果传入的是一个 EmployeeDao 的子类,而这个子类不会去访问数据库,那么 getEmployeeDto 这个方法就不会直接访问数据库,它就变成可测试的了。

比如传入这样的一个子类:

public class InMemoryEmployeeDao extends EmployeeDao {
  @Override
  public Employee getEmployeeById(long employeeId) {
    return null;
  }
}

这样,想测试原方法中 if 的代码逻辑就非常方便了。这里使用的重构手法叫做提取接缝(Extract Seam),至于什么是接缝,以及还有哪些可测试化重构的手法先按下不表,当代码可测了,就可以为它们添加足够的测试,提供质量保障。

然后,在测试的保障下进行安全的重构。接下来要做的就是将“祖传”代码重构得让人耳目一新。当代码结构良好了,再实现下一个代码现代化的目标,也就是良好的分层结构。


二、架构现代化

遗留系统现代化的第二个方向是架构现代化。看到“架构现代化”这几个字,很自然地就想到了微服务架构或云原生架构。然而前面说过,新不代表正确。在团队的开发能力、DevOps 能力和运维能力不足的时候,引入微服务,反而会将团队推向更痛苦的深渊。

有时候常常把软件系统比作一个城市,把系统架构和城市建设做类比。随着城市的发展和扩张,以前处于城市边缘的农村,反而会被周围新建的高楼大厦包裹成为一个城中村。治理这些城中村,就叫“改造老城区”。有时候老城区的设计和规划会暴露出一些问题,不足以满足城市的发展。比如市政府通过一些集中的招商引资后,很多企业都要来这里建厂,但老城区显然没有足够的空间。这时候很多城市都会新建一个城区,有些地方叫开发区,有些地方干脆直接就叫新区。将这称之为“建设新城区”。


同样,遗留系统的架构现代化,也可以分成“改造老城区”和“建设新城区”两类模式。

改造老城区模式是指对遗留系统内部的模块进行治理、让模块内部结构合理、模块之间职责清晰的一系列模式。前端方面包括单页应用注入、微前端等,后端包括抽象分支、扩张与收缩等,数据库端包括变更数据所有权、将数据库作为契约等。

在这里插入图片描述

建设新城区模式是指将遗留系统内部的某个模块拆分到外面,或将新需求实现在遗留系统外部的一系列模式。包括绞杀植物、冒泡上下文等。为了对新建立的新城区予以各种支持,老城区还可以通过提供 API、变动数据捕获、事件拦截等各种模式,与新城区进行集成。

在这里插入图片描述

只有“改造老城区”和“建设新城区”齐头并进,遗留系统架构的现代化版图才算完整。


三、DevOps 现代化

代码和架构现代化了,DevOps 的现代化也不能落后。它对项目的重要性不言而喻,如果没有现代化的 DevOps 平台,代码和架构现代化所带来的优势,就无法淋漓尽致地体现出来。假如在代码和架构优化后,需求的开发时间缩短了一倍,那么大家对于新需求上线的时间点自然也有新的期待。然而落后的 DevOps 水平反而会让这个时间变得更长,因为单体架构变成微服务了,DevOps 的难度增加了。

DevOps 的历史虽然只有短短十几年,但最近几年的发展势头却很足。大大小小的公司都开始了 DevOps 转型,很多项目都声称自己建立了持续集成流水线,但实际上很多都是只见其形不见其神,只学其表不学其里。而遗留系统的状况就更惨不忍睹了,它们几乎没有任何的自动化,或仅仅是一两句简单的构建命令。像我在第一节课里举的例子那样,在开发机上打包、靠人工用移动硬盘部署的项目还比比皆是。因此,遗留系统的 DevOps 现代化与其说是一种改进,不如说是从 0 到 1 的建设。

这一部分可以和代码、架构的治理并行,甚至可以更早。先把平台搭起来,再逐步往上添加内容。对于大多数遗留系统来说,有一个可以对代码进行构建、打包的流水线,就已经是极大的进步了。

要从头开始搭建一个 DevOps 平台,包括代码、构建、测试、打包、发布、配置、监控等多个方面。

这其中的代码和测试有一部分是和代码现代化重叠的,代码现代化的课里我会一并说给你听。剩下的几个部分再专门用一节课来详细讲述。


四、团队结构现代化

那这个团队结构现代化是个什么东西?其实很多时候,一个开发团队的结构是否合理,决定了这个团队的交付效率、产品质量,甚至项目成败,而很多人还没有对此产生足够的重视。

近年来有一本新书,叫做 Team Topologies,中文直译就是团队拓扑。一上市便引起了不小的轰动。它将团队放到了软件开发的第一位,提出了四种团队拓扑结构和三种团队交互模式四种团队拓扑包括业务流团队、复杂子系统团队、平台团队和赋能团队。

三种团队交互模式包括协作、服务和促进。我们在进行开发团队的组织结构规划时,应该参考这四种团队拓扑。去年这本书的中文版——《高效能团队模式》也已经上市了。

对于团队结构的现代化,基本上是围绕这本书的内容展开的。因为我发现,遗留系统中团队的问题,有时比遗留系统本身更大。

比如很多遗留系统可能只有一两个人在维护,在他们遇到困难的时候根本得不到团队的支持;再比如一些遗留系统的“老人”对系统比较熟悉,因此任何新启动的专项治理小组都会邀请他们加入,导致这些人的变动十分频繁,上下文切换的成本极其高昂。

团队拓扑不仅对遗留系统至关重要,对一个新系统如何组建开发团队、团队之间如何沟通协作也是至关重要的,后面我专门用一节课为你详细展开。


五、总结

本质上就是将先进的、现代化的软件开发方法应用到遗留系统上,让遗留系统重获新生、保持活力。是的,日光之下并无新事。

遗留系统之所以成为遗留的,就是因为既缺乏现代化的软件开发方法,又没有随着潮流的发展而不断演进。

在这里插入图片描述

遗憾的是,这里还应该引入一个“需求现代化”,但是在权衡之后我将它删除了。因为一个企业里的需求方与开发方是不同的部门,要想进行需求的现代化,必然要让需求部门参与进来。然而国内很多企业的需求部门和开发部门,还无法亲密无间地展开合作。甚至有信心对开发部门内部的团队结构进行重组,但却没信心让需求人员改变工作习惯。

无论如何,在做到代码、架构、DevOps、团队结构四个现代化之后,遗留系统的现代化之路就算基本成功了。

不过,在着手对这四个方面进行治理之前,还需要先掌握遗留系统现代化的三个原则。

即:

  1. 以降低认知负载为前提
  2. 以假设驱动为指引
  3. 以增量演进为手段

这是在工作中总结出来的,在遗留系统现代化中的许多举措,都符合这三个原则。

忽视了它们,四个现代化之路很可能背道而驰。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/436494.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【蓝桥杯省赛真题39】Scratch棒球本垒打 少儿编程scratch图形化编程 蓝桥杯省赛真题讲解

目录 scratch棒球本垒打 一、题目要求 编程实现 二、案例分析 1、角色分析

托福高频真词List01 // 附阅读真题

4月18日单词 🍮segregateseparateV.隔离🍮leftover remainingadj.剩余的 🍮rivalcompetitor 🍮rivalcompete n.对手🍮 v.对抗🍮 apparentlyseeminglyadv.显然 🍮unrivaledunequaledadj.无与伦…

Create a Process for Managing Support Cases

文章目录 前言&学习目标学习目标背景 一,Create Support Processes1.创建用户2.创建Processes3.创建Record Types4.创建升级规则(Escalation Rule) 前言&学习目标 记录trailhead学习重点,创建管理支持个案的流程,简化工作流并用新的…

mysql企业级安装部署(保姆级别教程)

前言 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系…

Spring——利用五大类注解和Bean注解实现更简单的存储

目录 一、配置扫描路径 二、使用注解存储Bean对象 2.1 Controller(控制器存储) 2.1.1 bean标签是否可以和component-san一起使用呢? 2.2 Service(服务存储) 2.3 Repository 2.4 Component 2.5 Configuration 2.6 五大类注解可以不在…

27岁,测试在职近5年,月薪不到2W,担心被应届生取代

工作了近5年,一个月工资不到20K,担心被应届毕业生取代!互联网的快速发展伴随着员工适者生存的加速,测试员的薪资也在不断增长,以3年、5年、8年为一条分水岭。如果人们的能力和体力不够,他们就会被淘汰。看起…

Android添加C++/CPP项目代码(2)

Android添加C/CPP项目代码(2) (1)选中某个module,右键,Add C to Module 在随后弹出的选项框中选第一个,OK。 (2)此时会在app下产生一个cpp目录和两个文件: x…

TiDB实战篇-BR进行数据备份与恢复

简介 使用BR进行数据备份与恢复。 原理 Backup备份的时候在PD上面找到表的元数据,然后找到对应的TiKV数据以后,直接备份到外部系统中(注意如果没有像HDFS这样的分布式文件存储,那么它每个TiKV备份到本地的文件就只有一部分数据&…

nssctf web 入门(10)

[NISACTF 2022]midlevel [NISACTF 2022]midlevel 尝试使用x-forwarded-for 发现可行判断是不是ssti 确定是ssti模板注入 [NISACTF 2022]is secret [NISACTF 2022]is secret 根据这个我们去看看secret 这里看到这个猜测是通过get传入secret的值然后会机密我们的值 我们发现传…

优秀软件方法学“漫游记”

你好,我是东(在极客时间的 ID 是 Fredo)。大学的时候我读的是计算机专业,现在是一名工作了近3年的程序员,很高兴能和你分享我的学习体会。 我是怎样学习课程的? 首先聊聊我是怎么学习这门课的。 DDD 是一…

智网工程师培训一些心得

智网工程师培训一些心得 MYSQLApache Flask开发小程序 MYSQL MYSQL的日期如果定义为datetime类型,比较的时候可以使用 entry_form.exercise_date between 2022-1-1 and 2023-4-19 日期可以是非2位宽对齐方式 日期比较也可以使用大于或者小于号MYSQL支持定时备份数…

学习数据结构第6天(栈的基本概念)

栈的基本概念 栈的定义栈的基本操作栈的存储结构 栈的定义 栈(Stack)是一种基于先进后出(FILO)或者后进先出(LIFO)的数据结构,是一种只允许在一端进行插入和删除操作的特殊线性表。 栈按照先进后出的原则存储数据,先进入的数据被压入栈底,最…

数据结构初阶(链表)

文章目录 一、链表的基础概念1.1 什么是链表1.2 分类1.3 链表的底层代码1.4 例题1.5 LinkedList 的实现(1)什么是LInkedList(2)底层代码(3)LinkedLIst的使用 1.6 ArrayList和LinkedList的区别 一、链表的基…

Spring底层架构核心概念

文章目录 Spring底层架构核心概念BeanDefinitionBeanDefinitionReaderAnnotatedBeanDefinitionReaderXmlBeanDefinitionReaderClassPathBeanDefinitionScanner BeanFactoryApplicationContext国际化资源加载获取运行时环境事件发布 类型转换PropertyEditorConversionServiceTyp…

20行Python代码获取 心碎榜单文件保存本地,准备开始emo......

人生苦短 我用python(emo版) (语气充满悲伤…) 今天咱们试试只用20行代码来实现 批量获取 某某云 文件保存本地,炒鸡简单! 悄悄的告诉你,其实不到20行代码~ 事前准备 软件环境 环境Python3.8编辑器是pycharm 模块…

轻松掌握k8s的kubectl使用命令行操作01知识点

程序员使用的kubectl,只能在主节点使用kubectl命令 1、查看集群所有节点 kubectl get nodes 2、根据配置文件,给集群创建资源 kubectl apply -f xxxx.yaml 3、查看集群部署了哪些应用 kubectl get pods -A 4、指定查看命名空间部署了哪些应用 不指…

[DSCoding2] 反转链表——迭代法

题目描述 核心思路 观察上图可以发现,将链表反转后,原有的结点间的引用关系发生了改变,比如反转前1指向2,反转后2指向1, 所以我们可以从修改节点间的引用关系下手。 在遍历链表时,将当前节点的next指针指向…

ReentrantLock原理

实现了Lock接口 内部也维护了一个同步器Sync继承自AQS,Sync是抽象的,两个实现NonFairSync和FairSync public ReentrantLock() {sync new NonfairSync(); } public ReentrantLock(boolean fair) {sync fair ? new FairSync() : new NonfairSync(); }非…

算法训练Day30:332.重新安排行程 51. N皇后 37. 解数独

文章目录 重新安排行程题解 [N 皇后](https://leetcode.cn/problems/n-queens/description/)题解 解数独题解 重新安排行程 CategoryDifficultyLikesDislikesContestSlugProblemIndexScorealgorithmsHard (47.57%)7650--0 Tags Companies 给你一份航线列表 tickets &#xf…

微服务学习——微服务

认识微服务 单体架构 将业务的所有功能集中在一个项目中开发,打成一个包部署。 优点: 架构简单部署成本低 缺点: 耦合度高 分布式架构 根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务。 优点: 降低服务耦合有利…