低代码到底是不是行业毒瘤?一线大厂怎么做的?戳此了解>>> 了解详情
写点什么

祖传代码迁移到 Vue 的另一种解题思路

2020 年 7 月 24 日

祖传代码迁移到Vue的另一种解题思路

随着时间的流逝,我们在应用程序中添加了许多新功能,应用程序变得越来越大,而技术环境在不断变化,各种新的框架、组件、架构不断涌现。当开发人员回头审视多年前写的代码时,你可能很想把它们全都丢掉并重写。但很多时候由于重写的风险和复杂性,这么做是不可能的,你必须找到一种让旧代码和新代码共存的方法。 本文 介绍了 THRON(一个用于管理数字资产和产品信息的 SaaS 产品)的研发团队在将他们的 Web 应用程序从 KnockoutJS 迁移到 Vue 的过程中,如何让旧代码和新代码共存的技术思路和实践。


本文最初发表在 Medium 博客,经原作者授权,InfoQ 中文站翻译并分享。


我们从 2013 年开始编写公司的主打产品。这个产品是我们写的第二个单页应用程序(SPA),之前写的第一个 程序 是一个小项目。我们分析了前面的经验,避免重蹈覆辙。


当时,浏览器和 js 库的大环境和今天不 太 一样,我们的企业客户目标 需要覆盖 IE9 用户,并且我们不信任大型复杂框架,因此更愿意采用一组独立的库。我们建立了自己的框架:灵活、易用,还有我们想要的 很多 功能,如数据绑定、模板、路由、国际化……


至于那些缺少的功能,我们可以自由选择自己喜欢的库,自然优缺点都要照单全收。这就是一种“选择太多”的情况。


我们写过的其他一些文章也提到了我们拆分单体应用程序的旅程:代码库影响前端代码架构


随着时间的流逝,我们在应用程序中添加了许多功能;我们变得越来越熟练,技术环境也在不断发展;但最重要的是应用程序变得越来越大。我们尽了最大努力来保持最高的代码质量,但正如你所知,一份代码过了多年之后,再看到它的开发人员 总 会想扔掉它然后重写的。对于大型 SPA (单页应用程序) 来说,由于重写的风险和复杂性,这么做是不可能的。我们必须找到另一种方式来满足新需求:


  • 我们希望能够在 不影响性能 的情况下向应用添加新的 “ 部分 ” (或称功能部分,section)。我们的 SPA 并未遵循“代码拆分”原则,因此每个新接口都被添加到了单个复杂代码块中。我们有太长时间只顾着添加功能了,当我们意识到应该做代码拆分时已经晚了:所有内容都紧密耦合在一起, 已经 无法拆分开 了 。

  • 我们希望能够采用 现代化、速度更快的框架 ,但更重要的是,我们要能自由更新或改变它们。对于许多复杂的接口来说,使用 7 年之久的库所提供的性能是无法与 Virtual DOM 之类的现代库所能达到的性能相提并论的。

  • 收集分析数据后,我们意识到市场已经足够成熟,所以可以将 支持的浏览器范围缩小到 IE11+ 。相比保留 IE9+支持的时候,这就带来了更多可能性。

  • 我们希望能 将代码的开发和维护任务分散到并行工作的不同团队成员之间 。业务不会 变慢 或停 滞 ,因此我们仍然希望能快速推出新功能,但我们希望新功能是用新库做的,因此必须找到一种让旧代码和新代码共存的方法。

  • 我们想要一个 更轻松、更敏捷的开发流程 。在当前架构中,需要添加某部分内容的开发人员可能必须了解整个架构才行,以免引入回 退 Bug 。我们 寻找 的解决方案必须允许组件之间更好 地 耦合,并让开发人员将学习范围缩小到他们的任务领域上。


但是我们如何在不停止产品发展的前提下实现所有这些目标呢?想要 在一个版本内重写整个应用程序是不可能的


我们使用的方法

我们将工作分为三个阶段:


第一步:从头开始,假装要重新设计一切

我们试着抛开现有的应用程序,从头开始想象一个新的架构,并参考流行的框架和著名的应用程序来获得灵感。


然后我们创建了概念验证(POC)解决方案来验证我们的需求。在开发它时我们添加了另一个要求:它必须提供应用 各部分管理层面的框架独立性 。也就是说我们 不能紧密绑定在特定框架上 。我们迟早会需要一个新框架来处理一些特殊的需求或提升可维护性,希望那一天到来的时候我们用不着再重构了。


POC 的想法是做一个“加载器”,它可以逐一导入我们需要的部分,同时尽量控制依赖项的数量,直到我们想要导入用不同框架编写的部分为止。在这种情况下,旧应用程序只不过是要加载的 其中 一个部分而已。


我们发现,既不想停下产品开发工作,又要对应用程序进行现代化改造,这就是唯一的选项:将旧的界面视为新应用程序中的一个特殊部分。


基于这些需求,POC 的核心就变成了一个“功能部分管理器”,它以 Vue Router 及其强大的导航保护为基础。当用户请求某个部分时,我们会检查其功能并加载正确的代码块。


旧 版 应用程序结构如下:



旧版 应用程序的加载过程


为了将旧的应用程序作为一个模块加载进来,我们需要做一些更改:


  • 旧的应用程序引导器是一个轻量级模块,用于管理应用程序启动相关的基本行为,例如资源加载、移动应用程序重定向、品牌颜色、安全偏好等。这是我们的 SPA 中唯一被完全重写的部分。这部分的语句不是很多,因此我们也借此机会删除了过时的逻辑。

  • 旧的应用程序使用了整个 HTML 正文来渲染接口。必须更改这个方法,让它在给定的 HTML 元素而不是整个正文部分上运行。

  • 我们被迫更改了导航 API。旧版应用程序使用的是 window.location.hash,而不是功能部分加载器 Vue Router 所使用的现代化 window.history.pushState。在第一阶段我们试图让它们共存,但在添加了 IE11 支持后我们搞出了一个“怪物”,连一致性都无法保证。我们发现唯一的解决方案是将对主路由的引用注入到刚加载的部分,这样整个应用程序导航都会使用 Vue 路由。


然后我们遇到了一个问题:新的应用程序允许我们只创建新的部分,这是可以创建最小实体,但在某些地方我们觉得它还是太大了。为什么我们只停留在“功能部分级别”呢?


于是 ,我们找出了希望使用新架构管理的三种方案:



为应用程序创建新部分的情况不是很常见,添加新的子部分(subsection)的频率则要高得多:


  • 在神奇的 CSS 的帮助下,我们可以仅在新接口中使用嵌套路由视图,在旧的部分使用现代化技术编写子部分。

  • 我们还规划了一种用新技术编写组件的方法,这些组件可以嵌入到旧版和新版的部分。我们使用了类似嵌入的方法,每个组件都有一个 init 方法,通过传递包含该组件的 DOM 元素来调用。


事后看来,这是一个非常好的主意,因为我们确实找到了可以让我们走向现代化开发的场景:



搜索区域是组件被用在不同部分或不同应用程序中的一个例子


第二步:更改代码库结构

开始开发 SPA 时我们有一个很好的想法,就是在组织源代码时将视图和控制器分离开来。随着时间的推移我们意识到,只要按各个部分简单分组,就可以帮助新加入的开发人员更方便地查看源代码和熟悉项目。


因此,我们先来整理存储库:


  • 托管标准的 Vue 项目结构,让刚开始接触代码库的开发人员或公司新人更容易理解代码(为什么选择Vue);

  • 按功能部分区分资源。旧版代码被降级到了一个文件夹中,我们的目标 是 逐渐减少那里的代码(当我们重构时),最终把文件夹挖空:所有旧代码 最终 都会转换成新的。


第三步:切换技术

实践证明,通过 POC 收集的经验非常宝贵。我们利用这些经验编写了真正的加载器,并在新加载器中将旧应用程序作为一个简单模块来运行。


但总有不顺心的事情。如果我们有机会重写整个遗留部分,就可以极大地提高性能,相反,在加载遗留部分时,我们的性能还是以前那个水平。最终用户并没有从此次升级中受益。


我们对整体重写感到不放心的原因之一是,自动化测试套件并未涵盖所有部分。而没有自动化测试套件就会错过很多边缘场景或用户模式,这可能导致代码中出现新的回 退 Bug 。这是我们日积月累总结出的教训:测试可以极大提升你对重构的信心,因为人类的记忆可能会出问题,但测试结果可以永存。


结论

到最后,事实证明采用现代 JS 框架是一项了不起的进步:开发人员可以专注于应用程序逻辑,而不是底层细节。对于每个新功能,我们现在需要维护的代码更少,并能为客户提供更好的性能。旧代码和新代码共存在独立于框架的组件中,这些组件共同构成了我们的 SPA。


新架构还为我们带来了其他改进:


  • 更简单的架构简化了优化工作。现在,我们更容易将一个较大的部分分割成一些较小的块,并且维护起来也更简单。

  • 开发人员不会感到无助:现代框架意味着充满活力和乐于助人的社区。

  • 更少的遗留代码意味着“黑匣子”也变少了,更多代码可以从更广泛的开发社区获得参考,从而提高了质量和可维护性。


我们的下一步将是改进测试套件。


你是否也有过类似的经历需要让新旧代码共存呢?你是如何应对的?请在评论区留言告诉我们 。


原文链接:


https://medium.com/thron-tech/keep-legacy-code-or-rewrite-a-middle-way-dc77c2f76e5f


2020 年 7 月 24 日 14:303374
用户头像
蔡芳芳 InfoQ高级编辑

发布了 560 篇内容, 共 266.1 次阅读, 收获喜欢 1731 次。

关注

评论 1 条评论

发布
用户头像
能不能放个demo出了
2020 年 08 月 25 日 17:17
回复
没有更多了
发现更多内容

OBS鉴权实现的宝典秘籍,速拿!

华为云开发者社区

OBS 签名

实战案例丨GaussDB for DWS如何识别坏味道的SQL

华为云开发者社区

数据库 sql 算子

分布式系统:数据一致性解决方案

马迪奥

分布式事务 一致性

面试官:哪些场景会产生OOM?怎么解决?

艾小仙

Java 架构 面试 编程语言 JVM

LeetCode题解:622. 设计循环队列,使用双向链表,JavaScript,详细注释

Lee Chen

LeetCode 前端进阶训练营

百度大脑助力旅游场景智能解决方案落地

百度大脑

2020年6月最新iOS面试题总结

iOSer

ios 2020 面试题 经验总结

Copy攻城狮辛酸史:含泪“一分钟”跑通MindSpore的LeNet模型

华为云开发者社区

学习 程序员 mindspore

同城快递订单系统架构设计方案

周冬辉

当代开发者的好帮手,浅析.NET敏捷开发框架的优势与特点

Philips

敏捷开发 软件开发 .net core 开发工具

Linux一键部署包,环境安装不用愁!!!

不才陈某

程序员 「Java 25周年」

git的几种实用操作(合并代码与暂存复原代码)

良知犹存

git

入行架构师之前,这7项技能你要先了解一下

华为云开发者社区

架构 架构设计 架构师

从想当亿万富翁到职场、创业、爱情、抑郁、学医学武,我的程序人生

陆陆通通

Java 创业 程序员 爱情 程序员生活

数字货币交易所技术开发,交易所源码

13530558032

Rust闭包的虫洞穿梭

袁承兴

rust 函数式编程 闭包

第一周作业,UML图

等燕归

架构师训练营第一期-第一周课后-作业一

跨专业学习6个月,成功上岸阿里|滴滴,分享学习路线供大家参考

小Q

Java 学习 架构 面试 基础

百度大脑事件图谱:洞察复杂世界中的事件知识

百度大脑

AWS在线技术峰会2020探班回顾,四大看点不容错过

郁敏

云计算 AI 云原生 金融 医疗

HTTP必知必会

陈靓-哲露

QPS、TPS、RT、并发数、吞吐量理解和性能优化深入思考

艾小仙

架构 编程语言

Java异常面试题(2020最新版)

Java架构师迁哥

未来已来!全球一流科技盛会——云栖大会9月17日线上隆重举办

北柯

学习笔记丨浮点数探究

Liuchengz.

C语言 基本数据类型

Java基础知识面试题(2020最新版)

Java架构师迁哥

食堂就餐卡系统设计 UML 练习

escray

学习 极客大学架构师训练营 UML

Spring-boot 单元测试

陈靓-哲露

架构师训练营第一期-第一周课后 - 作业二

卧槽!牛逼了!40K+Star!Mall电商实战项目开源回忆录!附源码、教程合集

云流

学习 架构师 计算机 程序员成长

2021 ThoughtWorks 技术雷达峰会

2021 ThoughtWorks 技术雷达峰会

祖传代码迁移到Vue的另一种解题思路-InfoQ