anjia

Slack 使用 TypeScript 的经验和心得 – 当多人协同编码时

anjia · 2017-05-15翻译 · 1026阅读 原文链接

当 Brendan Eich 在十天内为 Netscape Navigator 2.0 创建了 JavaScript 的第一个版本的时候,他很可能并没有想到 Slack 桌面应用程序能带它走多远:我们使用 JavaScript 代码库构建多线程桌面应用,与原生代码进行常规交互,针对 Windows、macOS 和 Linux 平台。

管理大量的 JavaScript 代码库是有挑战性的。不管什么时候,我们将对象从 Chrome 的 JavaScript 传递给 Objective-C 的时候,只需要在 Node.js 的不同线程里接收一个回调,我们需要确保各个部分能正常工作。在桌面的世界里,一个小小的错误很可能会导致应用程序崩溃。为此,我们采用了 TypeScript (一个静态类型的 JavaScript 超集),并很快就不用再担心了,而且爱上了编译器。不仅仅是我们,在 2017 年 StackOverflow 的开发者调查里,TypeScript 是第三个最受喜爱的编程技术。考虑到静态类型检查的速度越来越快,我们想要分享下我们的实战经验。

静态分析堪称救星

在过去,我们使用 JSDoc 记录函数签名,使用注释来说明类、函数和变量的目的和正确用法。这不是没有问题。看这样的代码,很难知道 JavaScript 解决了什么。我们不得不信任写代码的人正确地记录了文档,且后续修改代码的同时也正确地更新了文档。在有很多模块和依赖的复杂系统里,完全有可能做到搞坏一个函数而根本就不用打开它所在的文件。

为了改善这种境况,我们决定进行静态类型检查。静态类型检查器不会修改代码的运行时行为,它是分析你的代码,尽可能地推断出类型,并在代码发布之前给出警告。

静态类型检查器知道 **Math.random()** 返回一个数字,而字符串方法**toLowerCase()** 就不会(返回一个数字)。

为了更精准,类型检查器的使用者可以手动声明类型,来告知开发人员和机器代码的预期。下面的代码给“user”对象定义了一个接口,还定义了一个获取用户年龄的方法。静态类型检查器可以分析这样的代码,并对典型的人为错误予以警告,比如试图访问未定义属性。

有趣的是,代码在运行时并不会被修改,这意味着静态类型检查器不会为最终用户引入额外的开销。上面的例子在运行时看起来就像是原生 JavaScript:

智能静态类型检查器增加了我们对自己代码的信心,它能在代码提交之前检测出开发人员容易犯的错误,也能使代码具有更好的自我记录。

将 Slack 桌面代码库移植到 TypeScript

我们决定使用微软的 TypeScript,它将静态类型分析和编译器彼此结合。现代 JavaScript 代码就是有效的 TypeScript 代码,这意味着你可以在不修改一行代码的情况下使用 TypeScript。这样,我们就可以通过启用编译器和静态分析渐进使用 TypeScript 了,而不用中断关键 bug 的修复和新功能的开发。

实践中,在无需修改代码的情况下启用分析和编译器,意味着 TypeScript 会立即尝试理解你的代码。它使用内置类型和可用于第三方依赖的类型定义来分析代码的流程,指出之前被忽视的细微错误。一旦 TypeScript 遇到了没法理解的代码,它就会假定一个特殊类型“any”,然后继续进行分析。

原计划我们是慢慢地移植完文件,尽可能地将我们的原生 JavaScript 扩展成具有更具体的类型定义,诸如添加接口、定义私有或公有的类方法、声明枚举等。但是一路走来,我们有了两个惊人的发现:

首先,我们惊讶于在转换代码时发现的小错误的数量。和其他开始使用类型检查器的开发人员交谈的时候,我们很高兴听到这是一个共同的经验:人编写代码的行数越多,越容易拼错属性,越容易假设嵌套对象的父对象始终存在,也容易使用非标准的错误对象。

其次,我们低估了编辑器集成的强大程度。 感谢 TypeScript 的语言服务,具有自动补全功能的编辑器可以支持具有上下文感知建议的开发。TypeScript 知道特定对象的可用属性和方法,让你的编辑器也这样做。一个只使用当前文件中单词的自动补全系统毕竟有点野蛮。AtomVisual Studio CodeSublime 都有可用的插件,几乎所有的其他编辑器也有。不离开编辑器就能验证代码可以立即提高我们的生产力。

展望未来,想想代码的可维护性,我们感谢围绕 TypeScript 的生态系统。作为 React 和 Node/npm 生态系统的重度用户,第三方库的类型定义的可用性是个很好的加分项。我们导入的很多库都已经兼容 TypeScript 了。 如果类型定义没有随模块本身一起提供,那它们很可能在非常给力的 DefinitelyTyped 项目中可以找到。例如,React 不附带类型定义,但是一行简单的命令 **npm install @types/react** 就可以安装它们,而不需要进一步配置。

TypeScript 使代码更稳定更合理,对我们来讲它简直就是个恩赐。所以,在移植开始的几天内,我们的所有新代码都用了 TypeScript。注释桌面应用程序代码库的绝大部分 JavaScript 大约耗时六个月。

自信地提交

为了强制保持可读性和可维护性,所有的已暂存代码都会自动被 TSLint 检查(TSLint 作为预提交的钩子),这意味着在 Git 提交更改之前,它会首先检查更改的代码是否通过我们的 linting 规则。我们不允许使用“隐含的 any 类型”,也就是说现在要求所有的 Slack 桌面代码显式地声明类型,以避免 TypeScript 无法自动推断出类型。

当推送分支的时候,Git 首先针对整个代码库运行 TypeScript 编译器,该编译器会分析代码的结构和功能错误,并将现代特性(如 async/await)转换为 ES2016 兼容的代码。 当拉取代码的时候,我们已经确信代码中的结构性依赖是健全的。

一点担心

对我们来说,TypeScript 的好处大大超过了它的缺点。嗯,缺点确实存在。最值得一提的是额外的培训成本。使用过任何强类型语言的开发人员通常会在一两个小时之内学会这种语法,但对于原生 JavaScript 背景的开发人员来说,想充分利用所有的 TypeScript 特性可能会让人望而生畏。

对于这个问题,最明显的解决方案是慢慢地逐步击破各个功能。你可以只是简单地启用 TypeScript,而不更改任何代码,然后,再开始添加一些简单的类型声明,再在特定的模块或后续要提交的模块里,尝试更复杂的概念,例如继承(inheritance)、泛型(generics)以及高级类型(交集类型,映射类型)。最后,我们的经验是你可以通过使用最基本的 TypeScript 用法获得很多好处。

回馈

在 Slack,我们努力做优秀的开源公民。为此,我们力求让其他开发人员更容易地使用 TypeScript:每当找到差距,我们就会尝试关闭它们。

最值得注意的是,Slack 自己的电子编译允许 Electron 应用程序的开发人员使用 TypeScript 编写,而不用担心编译本身。RxJS(一个 Reactive 扩展库)在 Slack、Netflix、GitHub 和许多其他公司大量使用,在 Slack 的支持下也在向 TypeScript 转移。我们的桌面工程师编写的许多小型库也都慢慢获得了 TypeScript 的支持,比如 spawn-rx电子拼写检查电子遥控电子通知状态电子窗口通知

要开始使用 TypeScript,可查看官方手册。如果你想知道实践中的小型 port 长什么样子,可以看看 spawn-rx 的 port 一瞥。如果你想为 Electron 应用程序编写你的第一行 TypeScript,可以看看优秀的 electron-forge,它实现了电子编译,支持 TypeScript 开箱即用,它甚至有一个优秀的 React/TypeScript 模板。 electron-forge 是我们在 Slack 桌面团队中非常喜欢的一个架构。如果你觉得混合使用现代 web 技术和原生代码来构建跨平台的桌面应用程序是一件令人兴奋的事儿,那么来和我们一起工作吧

译者anjia尚未开通打赏功能

相关文章