作者 | Caleb Meredith,Andrew Wang

译者 | 弯月      责编 | 欧阳姝黎

出品 | CSDN(ID:CSDNnews)

以下为译文:

最近,为了紧跟时代发展的脚步,我们决定引入新技术和模式,并将我们的代码库迁移到TypeScript。

上帝说:要有光

这个故事要从最初我们创建代码库说起:

commit 681429d64232f44c6af1a1d838b91fe39d52edb0
Author: Howie <howie@Howie-Lius-MacBook-Air.local>
Date:   Sun Apr 22 15:55:06 2012 -0700
beginning state

2012 年,在服务器端使用 JavaScript 的想法仍然相对较新。记得那一年苹果发布了 iPhone 5,鸟叔的一首《江南 Style》红遍全世界,成为互联网上首个播放量超过 10 亿的视频。而我们的创始人则利用首代技术栈开始了 JavaScript以及 JavaScript 生态系统的冒险之旅。

在当初选择的技术中,有些已经相当成熟(比如 Node.js、Express和JavaScript 本身),而有些则仍然很年轻(比如 Backbone、Underscore.js、EJS 和 jQuery)。在过去的九年中,通过不断的重构工作,即使公司已经发展到一百多名工程师,代码量也超过了一百万行,但我们的代码库仍然是统一用相对现代的 JavaScript 语言编写而成的。

核心代码库的展示:扩展记录

下面,我来举个例子。我们的代码库有一个扩展记录的功能,是于 2012 年引入的。此后,核心功能几乎保持不变,但代码的结构已发生了很大变化。如下是过去 9 年中扩展记录功能的前 50 行代码:

我们可以通过这段代码大致了解一下我们的代码库:

  • 2012 年:首次提交到代码库;

  • 2015 年:更多人开始使用该代码库,我们建立了一些编程约定;

  • 2016 年:引入 Browserify,并添加了明确的 CommonJS 导入;

  • 2018 年:从 Backbone 样式类转换为 ES6 样式类;

  • 2019 年:自定义组件框架转换为 React 组件;

  • 2019 年:从 Flow 迁移到 TypeScript,CommonJS 导入替换为 ES6 导入;

  • 2021 年:使用类 React 组件替换了 createReactClass 和 mixins。

通过上述扩展记录的代码发展历史,相信你可以看出,我们愿意大规模地重构我们的代码库。我们认为无法维护的代码是不可避免的。我们认为保证代码质量是全体工程师的责任。比如,上述扩展记录的代码变化就是由不同团队的不同工程师在过去的几年中付出的心血。

接下来,我详细介绍一下,我们将代码库迁移至 TypeScript 的整个过程。

押错了宝

最初我们的代码库采用了原始的 JavaScript。静态类型的好处就不用我再重复了。2016 年,在我们研究 JavaScript 的增量类型时,Flow 和 TypeScript 这两匹黑马脱颖而出,一时之间难分上下。我们之所以选择 Flow,是因为在当时它对 React 的支持更好。

时至 2019 年,我们发现自己押错了宝。TypeScript 的发展速度远远超过了 Flow,而且 TypeScript 在功能、IDE 支持和社区资源等方面也展现出了明显的优势。于是,我们决定将代码库迁移至 TypeScript。

指导原则

如今,我们的代码库包含一百多万行 JavaScript 代码。考虑到规模以及复杂性,我们决定主要围绕以下三个原则开展迁移工作:

  • 不能破坏产品。保留现有代码的语义,以避免引入面向客户的问题。

  • 不能降低类型安全。与 Flow 相比,迁移中的每个更改都必须提高类型安全性。虽然可能仍然会有一些不安全的代码存留下来,但是每个更改都应保证或提高已有的类型安全。

  • 尽量保持简单。迁移需要大量代码改动。每个改动都应该尽量保持简单,而且应该以文件为单位推进。

大规模的迁移

一般情况下,TypeScript 的迁移都是增量逐步完成的,即逐个类型、逐个文件进行。由于我们的大部分代码库已经转换成了 Flow 类型,因此我们采用了另一种方法:一次性将整个代码库迁移至 TypeScript,这是一次大规模的迁移。

第一步,我们编写了一个小工具,完成了代码的纯机械转换。虽然有一些 Flow转换为 TypeScript 的工具,但我们还是编写了自己的工具,为的是满足我们的一些特定需求:

  • 现有的工具不会修改模块语法。我们首先需要将 CommonJS 模块语法(require()和 module.exports)转换为 ES 模块语法(import 和 export)。

  • 一些现有的工具对 Flow 的处理并不正确。例如,他们将 Flow 的类型强制转换表达式(x: T)(这个转换是协变的,covariant)转换成了 TypeScript 的类型强制转换表达式 x as T(这个转换是双变的,bivariant),这种做法是不安全的!而我们的工具则使用了自定义的工具函数,类似于 cast<T>(x),实现为 function cast<T>(x: T): T { return x }。

  • 此外,我们还希望进行一些内部特有的处理。例如,我们经常使用{[key: UserId]: string}等类型,但 TypeScript 并不支持自定义索引访问类型。因此,我们将这些转换成了 Record<UserId, string>(而不是{[key: string]: string})。

我们的工具最有技术含量的一个功能是,它处理未声明类型的函数参数的方式。例如,请看以下示例,参数x没有指定类型:

function f(x) {
return x * 2;
}

在这种情况下,Flow 可以根据上下文推断x是一个数字。但是,TypeScript 不会,而且在严格模式下还会报错。

我们的某些代码利用了 Flow 的这一功能。由于我们的指导原则之一是“不能降低类型安全”,因此所有带有 any 参数的函数都需要改。我们的工具通过执行 flow type-at-pos,使用 Flow 推断的类型来标注每个未声明类型的函数参数。事实证明,大多数情况下,Flow 可以推断出 any 的类型。

撸起袖子开干

我们的工具完成了大部分必要的改动,总共修改了 3300 个文件。但是,仍然还有很多无法自动处理的地方。tsc 运行的结果显示,1600 多个文件引发了 15000 多个 TypeScript 错误,我们必须手动修改了。

幸运的是,我们找到了一位类型系统专家,他花了大约一个星期的时间,专门坐下来修复 TypeScript 的错误。整个过程枯燥又乏味,但是总好过使用 // @ts-ignore 搞乱代码。看来,我们树立指导原则还是很有必要的,我们不能让现有 Flow 代码的类型安全性倒退(原则 2:不能降低类型安全),但是为了提高类型安全性而积极地重构实际上也很危险(原则 1:不能破坏产品)。由于首要原则是不能破坏产品,因此有时添加 // @ts-ignore 是最好的解决方案。

所有这些工作都是在单独的分支上完成的。在分支通过类型检查和自动化测试之后,修改就可以反映到主分支上了。

由于手动检查 1600 多个文件(以及 4.8 万行代码)的改动不太现实,因此我们结合使用了很多工具:

  • 将 14 个自动转换,以及 17 个类别的手动转换写成文档,然后要求公司工程师审阅该文档。

  • 每个领域的专家最多会被分配 10 个文件,然后进行代码审核。

  • 对修改前后的代码编译成的 JavaScript 包的差异进行代码审核。我们的编译技术栈没有变化(Webpack 和 Babel),因此编译后的软件包只有一些无足轻重的变动。

2019 年 10 月底,我们冻结了主分支,重新运行了代码自动转换,并合并了 TypeScript 分支。从那以后,我们就正式迈入了 TypeScript。

尘埃落定

由于我们的第三项原则是:尽量保持简单,因此其他的改进想法都被推迟了。我们的一位工程师撰写了一份大约包含 20 种修改思路的文档。令我们十分自豪的是,在过去的两年中,我们的工程师们靠自己的努力实现了一半以上的修改方案。下面,仅举几个例子:

  • 你可以看到在上述代码片段中,我们于 2021 年初将 createReactClass 组件全部转换成了 React ES6 类。这是一项艰巨的任务,因为代码库的很多地方都使用了 createReactClass。我们的开发团队发现自定义类型 createReactClass 严重影响到了 TypeScript 的构建时间,于是,他们非常高效地完成了全部修改。

  • 我们的自动化团队编写了一个辅助方法,可以根据给定的定义,为我们的内部对象定义验证框架生成 TypeScript 类型。在这之前,我们需要同时维护定义和 TypeScript 类型,这可能会导致两者的不一致。

  • 我们的企业团队将所有文件扩展名都转换成了.tsx。作为一个团队,我们认为.ts和.tsx代表 TypeScript 语言的两种形式,所以我们决定只采用一种形式。

  • 我们的一位创始人增强了我们的 MySQL 数据库访问层,可以返回带有类型的查询结果。在 TypeScript 3.9 发布之后,他们还将所有// @ts-ignore注释升级为 @ts-expect-error。

  • 我们的生态系统团队正在努力通过工具来激活--noUncheckedIndexAccess,这是 TypeScript 4.1 中的一项新功能。

总结

从长远来看,低代码/无代码应用程序开发平台是一种趋势,我们相信在未来几十年中我们还将在该领域继续创新。我们非常重视代码的质量,并希望不断发展我们的代码库。

TypeScript 迁移是我们的代码库所经历的一次最大的重构,但肯定不是最后一次。

链接:https://medium.com/airtable-eng/the-continual-evolution-of-airtables-codebase-migrating-a-million-lines-of-code-to-typescript-612c008baf5c

声明:本文由CSDN翻译,转载请注明来源。

60+专家,13个技术领域,CSDN 《IT 人才成长路线图》重磅来袭!

直接扫码或微信搜索「CSDN」公众号,后台回复关键词「路线图」,即可获取完整路线图!

押错宝!一次性将百万行代码从 Flow 迁移至 TypeScript相关推荐

  1. 将 30 万行代码从 Flow 迁移到 TypeScript 是一种怎样的体验?

    作者 | Roger Goldfinger 译者 | 弯月 责编 | 伍杏玲 出品 | CSDN(ID:CSDNnews) [CSDN 编者按]本文作者在当前正在使用 Flow 下,想一次性切换到Ty ...

  2. 如何将三万行代码从Flow移植到TypeScript?

    作者 | David Gomes 译者 | 弯月 责编 | 郭芮 来源 |  CSDN(ID:CSDNnews) [编者按]在内存安全中,类型安全是很重要的一个命题.为了确保JavaScript项目运 ...

  3. 如何将三万行代码从 Flow 移植到 TypeScript?

    [CSDN编者按]在内存安全中,类型安全是很重要的一个命题.为了确保JavaScript项目运行的类型安全,本文的作者介绍了2016年时使用Flow的经历:由Facebook支持的Flow方案,不仅拥 ...

  4. 一台微型计算机_Linux的上百万行代码,一台新的微型计算机以及Google和Microsoft的更多产品

    一台微型计算机 在本周的开源新闻摘要中,我们介绍了用于Linux的一百万行代码,来自Google和Microsoft的更多开源软件,用于教育的新型微型计算机等等! 开源新闻:2015年7月4日至7月1 ...

  5. ONAP如何将Open-O和ECOMP数百万行代码合并?

    今年2月份,Linux基金会旗下两大开源MANO工作组Open-O和ECOMP日前宣布正式合并成为一个组织,开放网络自动化平台(ONAP). 早在去年,AT&T就谋求将其生产环境中使用的ECO ...

  6. .net 本地文件管理 代码_如何在百万行代码中发现隐藏的后门

    试想一下,如果你的网站被入侵,攻击者留下隐藏的后门,你真的都可以找出来嘛?面对一个大中型的应用系统,数以百万级的代码行,是不可能做到每个文件每段代码进行手工检查的. 即使是一款拥有99.9%的Webs ...

  7. 开源50万行代码,百亿广告分成,百度智能小程序能成吗?

    作者 | 非主流 出品 | AI科技大本营 终于,BAT 在小程序的赛道上展开了激战,而这一场战争得到了百度前所未有的重视. 9 月 4 日,百度总裁张亚勤称拉动百度业务的"新四小龙&quo ...

  8. 数百万行自研代码都捐了,华为将欧拉捐赠给开放原子开源基金会

    今天,操作系统产业峰会2021在北京国家会议中心线上线下同步举办.会上,华为携手社区全体伙伴共同将欧拉开源操作系统(openEuler, 简称"欧拉")正式捐赠给开放原子开源基金会 ...

  9. 35 万行代码,旷视重磅开源天元深度学习框架 ,四大特性实现简单开发

    [导读]2020 年 3 月 25 日,人工智能企业旷视科技举办线上发布会,旷视联合创始人兼 CTO 唐文斌宣布正式开源其 AI 生产力平台 Brain++ 的核心组件--天元(MegEngine). ...

最新文章

  1. Linux 性能分析大概步骤
  2. <scope>test</scope>的作用
  3. pikachu安装以及安装时遇到的的问题——pikachu数据库链接不上config.inc.php 以及侧边栏访问没有反应,只有URL加了个#原因和解决办法
  4. BZOJ1354: [Baltic2005]Bus Trip
  5. spring interceptor 拦截方法,判断用户是否存在
  6. 关于eclipse新建web项目,提示:The superclass javax.servlet.http.HttpServlet was not found on the Java解决办法...
  7. html网页设计插件,适用于网页设计的Photoshop插件包
  8. logo是啥_logo什么意思_LOGO是什么意思
  9. 名侦探柯南主线剧情整理
  10. c++实现剧情小游戏:哈利波特
  11. UR机器人装箱姿态_ur机器人坐标系说明来了,感兴趣的朋友可以看看
  12. JAVA+MySQL综合笔记
  13. RGB和HSV颜色模型
  14. Mal-PEG3-acid,518044-40-1亲水性PEG间隔物增加了在水介质中的溶解度
  15. 图像压缩之奇异值分解(SVD)
  16. Java集合,Collections
  17. 学编程要什么基础,零基础呢?
  18. 代理服务器列表 20100116
  19. vmware——vsphere 安装图形界面出现双鼠标问题(vsphere client 5.5)
  20. NBA之spark-hive解析答案

热门文章

  1. 模版 ----- 实数二分
  2. LeetCode 面试题 08.01. 三步问题 (动态规划)
  3. 第一个程序python-HelloWorld
  4. J2EE代码存档--导出Excel
  5. hbase 监控指标项
  6. 浅谈Javascript -- 【嵌套函数及闭包】
  7. 函数指针和指针函数(回调函数)
  8. linux 开机 找不到 文件系统 下载文件系统就好了,开机启动找不到文件系统的修复步骤...
  9. oracle 建立一个游戏库,Power Designer怎么新建Oracle数据?建立Oracle数据教程分享
  10. pointnet2(pointnet++)源码复现