接下来,我具体先容一下,我们将代码库迁徙至 Type 的整个过程。
押错了宝
最初我们的代码库接纳了原始的 Java。静态范例的利益就不消我再重复了。2016 年,在我们研究 Java 的增量范例时,Flow 和 Type 这两匹黑马脱颖而出,一时之间难分上下。我们之以是选择 Flow,是由于在其时它对 React 的支持更好。
时至 2019 年,我们发现本身押错了宝。Type 的发展速率远远凌驾了 Flow,而且 Type 在功能、IDE 支持和社区资源等方面也显现出了显着的上风。于是,我们决定将代码库迁徙至 Type。
引导原则
现在,我们的代码库包罗一百多万行 Java 代码。思量到规模以及复杂性,我们决定重要围绕以下三个原则开展迁徙工作:
- 不能粉碎产物。保存现有代码的语义,以制止引入面向客户的题目。
- 不能低落范例安全。与 Flow 相比,迁徙中的每个更改都必须进步范例安全性。固然大概仍旧会有一些不安全的代码存留下来,但是每个更改都应包管或进步已有的范例安全。
- 只管保持简朴。迁徙必要大量代码改动。每个改动都应该只管保持简朴,而且应该以文件为单元推进。
大规模的迁徙
一样平常环境下,Type 的迁徙都是增量渐渐完成的,即逐个范例、逐个文件举行。由于我们的大部门代码库已经转换成了 Flow 范例,因此我们接纳了另一种方法:一次性将整个代码库迁徙至 Type,这是一次大规模的迁徙。
第一步,我们编写了一个小工具,完成了代码的纯机器转换。固然有一些 Flow转换为 Type 的工具,但我们照旧编写了本身的工具,为的是满意我们的一些特定需求:
- 现有的工具不会修改模块语法。我们起首必要将 CommonJS 模块语法(require和 module.exports)转换为 ES 模块语法(import 和 export)。
- 一些现有的工具对 Flow 的处置惩罚并不精确。比方,他们将 Flow 的范例逼迫转换表达式(x: T)(这个转换是协变的,covariant)转换成了 Type 的范例逼迫转换表达式 x as T(这个转换是双变的,bivariant),这种做法是不安全的!而我们的工具则利用了自界说的工具函数,雷同于 cast<T>(x),实现为 function cast<T>(x: T): T { return x }。
- 别的,我们还盼望举行一些内部特有的处置惩罚。比方,我们常常利用{[key: UserId]: string}等范例,但 Type 并不支持自界说索引访问范例。因此,我们将这些转换成了 Record<UserId, string>(而不是{[key: string]: string})。
我们的工具最有技能含量的一个功能是,它处置惩罚未声明范例的函数参数的方式。比方,请看以下示例,参数x没有指定范例:
在这种环境下,Flow 可以根据上下文推断x是一个数字。但是,Type 不会,而且在严酷模式下还会报错。
我们的某些代码使用了 Flow 的这一功能。由于我们的引导原则之一是“不能低落范例安全”,因此全部带有 any 参数的函数都必要改。我们的工具通过实行 flow type-at-pos,利用 Flow 推断的范例来标注每个未声明范例的函数参数。究竟证实,大多数环境下,Flow 可以推断出 any 的范例。
撸起袖子开干
我们的工具完成了大部门须要的改动,统共修改了 3300 个文件。但是,仍旧另有许多无法主动处置惩罚的地方。tsc 运行的效果表现,1600 多个文件引发了 15000 多个 Type 错误,我们必须手动修改了。
荣幸的是,我们找到了一位范例体系专家,他花了约莫一个星期的时间,专门坐下来修复 Type 的错误。整个过程枯燥又乏味,但是总好过利用 // @ts-ignore 搞乱代码。看来,我们树立引导原则照旧很有须要的,我们不能让现有 Flow 代码的范例安全性倒退(原则 2:不能低落范例安全),但是为了进步范例安全性而积极地重构现实上也很伤害(原则 1:不能粉碎产物)。由于主要原则是不能粉碎产物,因此偶然添加 // @ts-ignore 是最好的办理方案。
全部这些工作都是在单独的分支上完成的。在分支通过范例查抄和主动化测试之后,修改就可以反映到主分支上了。
由于手动查抄 1600 多个文件(以及 4.8 万行代码)的改动不太实际,因此我们联合利用了许多工具:
- 将 14 个主动转换,以及 17 个种别的手动转换写成文档,然后要求公司工程师审视该文档。
- 每个范畴的专家最多会被分配 10 个文件,然后举行代码考核。
- 对修改前后的代码编译成的 Java 包的差别举行代码考核。我们的编译技能栈没有变革(Webpack 和 Babel),因此编译后的软件包只有一些无足轻重的变更。
2019 年 10 月尾,我们冻结了主分支,重新运行了代码主动转换,并归并了 Type 分支。从那以后,我们就正式迈入了 Type。
灰尘落定
由于我们的第三项原则是:只管保持简朴,因此其他的改进想法都被推迟了。我们的一位工程师撰写了一份约莫包罗 20 种修改思绪的文档。令我们非常自大的是,在已往的两年中,我们的工程师们靠本身的积极实现了一半以上的修改方案。下面,仅举几个例子:
- 你可以看到在上述代码片断中,我们于 2021 年初将 createReactClass 组件全部转换成了 React ES6 类。这是一项困难的使命,由于代码库的许多地方都利用了 createReactClass。我们的开辟团队发现自界说范例 createReactClass 严峻影响到了 Type 的构建时间,于是,他们非常高效地完成了全部修改。
- 我们的主动化团队编写了一个辅助方法,可以根据给定的界说,为我们的内部对象界说验证框架天生 Type 范例。在这之前,我们必要同时维护界说和 Type 范例,这大概会导致两者的不同等。
- 我们的企业团队将全部文件扩展名都转换成了.tsx。作为一个团队,我们以为.ts和.tsx代表 Type 语言的两种情势,以是我们决定只接纳一种情势。
- 我们的一位首创人加强了我们的 MySQL 数据库访问层,可以返回带有范例的查询效果。在 Type 3.9 发布之后,他们还将全部// @ts-ignore解释升级为 @ts-expect-error。
- 我们的生态体系团队正在积极通过工具来激活--noUncheckedIndexAccess,这是 Type 4.1 中的一项新功能。
总结
从久远来看,低代码/无代码应用步伐开辟平台是一种趋势,我们信赖在将来几十年中我们还将在该范畴继承创新。我们非常器重代码的质量,并盼望不停发展我们的代码库。
Type 迁徙是我们的代码库所履历的一次最大的重构,但肯定不是末了一次。
链接:http://medium.com/airtable-eng/the-continual-evolution-of-airtables-codebase-migrating-a-million-lines-of-code-to-type-612c008baf5c返回搜狐,检察更多