原文链接:https://blog.aria.ai/post/why-use-flow/
Flow是一个由 Facebook 开源的 JavaScript 静态类型检查器。旨在解决JavaScript编程中的多种痛点,写出更优雅、更易理解的 JavaScript 代码。
引用 Flow 主页上的介绍:
Flow 可以在 JavaScript 代码运行之前检测错误,包括
- 静态类型转换,
- 空指针引用,
- 和可怕的调用函数未定义的错误
以及
Flow 还会逐步地将类型断言融入到你的代码中
所以 Flow 可以解决很多常见的 JavaScript 问题,你可以逐步将其引入你的代码库中。这很酷吧!
类型
在我们使用Flow之前,我们要先弄清楚什么是类型。我们看一下维基百科上的数据类型文章中的定义:
类型是用来标识各种不同种类的数据的,如实数类型、整型或布尔型。它可以确定该类型的可能值,可对该类型的值进行的操作,其数据的含义,以及该类型的值可用的存储方式。
用我自己的话简单来讲,类型就是你程序中约束数据的规则,这些规则帮助计算机确定你可以在数据上做哪些事情,不能做哪些事情。如果你不小心试图破坏这些规则,它可以提醒你,这一点大有裨益。
当你使用不同的语言编写代码时,你会发现,类型的表现方式可能会迥然不同,从必须声明到可选,再到几乎不需要。通常,编程语言的类型系统分为两类:强类型 vs. 弱类型,以及动态类型 vs. 静态类型。
强类型 vs. 弱类型
维基百科有一篇很好的文章介绍这一点,目前人们普遍认为,强类型和弱类型有些模棱两可,因为二者之间还没有一个成文的约定。我决定按前面讲到的那篇维基百科上的文章来讲。
隐式类型转换和“类型双关”
在像 python 这样的强类型语言中,变量在第一次声明之后就不能再改变它的类型,除非你临时显式转换为其他类型,或者之后重新声明。
1 | x = 5 |
这样会抛出下列错误
1 | `TypeError: unsupported operand type(s) for +: 'int' and 'str'` |
但这样写是可以的
1 | x = 5 |
或者这样
1 | x = 5 |
在 JavaScript 等弱类型语言中,由于变量在使用时被隐式转换,会变得更加灵活。你可以将字符串和对象相加,数组和对象相加,数值和null相加。更糟糕的是,运算出错时并不会抛出异常。
1 | console.log({} + {}) // NaN |
我想你能想象到可能发生的各种问题,而这些问题都不会抛出错误。
动态类型 vs. 静态类型
动态类型 vs. 静态类型比强类型 vs. 弱类型有更多的争议。我不会说二者哪个更好,也不会逐一全面分析它们的优点。相反,我只是简单介绍一下二者的优点,如果你想了解更多关于它们二者哪个更好的辩论,可以看看下面的文章。
现在给出我自己的免责观点:
在静态类型的语言中,你需要显式地写出变量的类型。很多人都了解 Java 这种强类型的静态类型语言,你需要在 Java 语言中写明变量类型,如int
或String
,以及函数的返回值类型和参数类型,如int add(int a, int b)
:
1 | public class Hello { |
因为String
类型和int
类型不能相加,这段代码在编译时第8行会报错。
注意:
- 错误在编译的时候就会捕获,而不是这代码运行的时候,这意味着,只有你修复了错误才能运行代码。
- 如果你使用IDE编写代码,IDE会提示你
add(x, s)
写法错误。因为你事先指定了语言类型,所以IDE可以在更高级别进行分析,而无须编译,以发现错误。
- 如果函数被命名为其他很随意的名字而不是
add
,你仍然可以看出它需要接受两个整型参数并返回一个整型结果,这是非常有用的信息。
在动态类型语言中,你根本不用指明变量的类型。其主要好处是你的代码不会那么混乱,你不必在开始编程之前考虑类型,这提升了生产力。python 就是一门强类型的动态类型语言,下面的代码可以实现与上一段代码相同的功能。
1 | def main(): |
运行代码时,这段代码将在第7行抛出错误,因为string
类型和int
类型是不能相加的。
值得注意的几点:
- 这样写代码更加简洁。
- 你不能确定变量
a
和b
的类型,int
、string
、float
等类型都是有可能的。 - 这段代码仍然会在运行时抛出错误,注意是在运行时而不是在编译时,这与静态类型语言比较是一个很大的区别。也意味着测试对于动态类型的语言更加重要,因为即使代码包含类型错误,也可能不会有太大问题。
静态类型语言中的类型推断
我之前说过,静态类型语言需要显式地写出类型,这句话并不完全正确。在没有类型推断的语言(如 Java )中,这是对的,但在类型推断语言中,计算机可以帮助你来确定使用什么类型。例如,在下面的例子中,是用 Haskell 写的一段与前文代码功能相同的一段代码,这是一门以其真正强大的类型系统而闻名的语言。在我写 let x = 1
和
let add’ = (+)`时,Haskell 会自动推断其类型,而不需要显式写出。
Haskell
1 | main :: IO() |
在 Flow 等其他类型系统中都有类型推断。然而,即使类型推断让你的编码更加简单,编码量更加少,你也不应该完全依赖于它。
回到JavaScript和Flow上面来
现在我们了解了更多关于类型的东西,可以回到手头上的问题来,这会让你在写 JavaScript 的时候犯更少的错误。
JavaScript 既是弱类型语言又是动态类型语言,这是一个灵活但又及其容易出错的组合。正如前文所说,由于隐式转换的原因,不同类型的值之间所有的操作都不会抛出错误,无论这些操作是否有效(弱类型),并且在从来不用显式地写出类型(动态类型)。
这种动态类型和弱类型的大杂烩是相当不幸的,从下面的例子和许多人对这门语言的批判中就可以体现。
这些问题的大多数解决方案是 Flow,通过静态类型和类型推理,如前文所述,解决了许多语言的痛点。
这不是一篇教程,如果你需要教程可以移步flow入门指南。
让我们继续分析,回到我们在讲弱类型部分的第一个JS示例,这次我们使用 Flow 来检查代码。
我们将// @flow
添加到代码的第一行,然后使用命令行工具flow
来检查代码(集成在IDE中也是有可能的):
1 | // @flow |
于是每一行代码都会立即生成类似于下面的错误。
1 | index.js:3 |
不需要做任何额外的工作来添加类型注释,Flow 就已经指明了代码中的错误。wat
视频中提到的那些问题也不会再出现。
注释代码的好处
虽然 Flow 会帮助发现上面的错误,但是你要想真正从中获益,你必须自己编写类型注释,这意味着你可以使用 Flow 的内置类型,如number
、string
、null
、boolean
等等,来指定值的类型或自定义某些类型别名,请看下面的例子:
1 | type Person = { |
现在你可以将函数:
1 | function xyz(x, y, z) { |
转换成
1 | // @flow |
在上述实例中,我们知道函数的参数x、y、z应该接受三个数值,并且其返回值也应该是数值。如果你试图这样传参,xyz({}, '2', [])
,在JavaScript是百分之百可以的,而 Flow 则会抛出错误。随着你越来越多地开始这样写,Flow 会更加了解你的代码库,并更好地提示你代码中的错误。
一些例子
捕获函数参数数量不正确的错误。
代码如下:
1 | // @flow |
报错如下:
1 | index.js:7 |
捕获函数参数类型不正确的错误。
代码如下:
1 | // @flow |
报错如下:
1 | index.js:7 |
确认你不要忘记检查 NULL。
代码如下:
1 | // @flow |
报错如下:
1 | index.js:11 |
确认你的返回值是否正确。
代码如下:
1 | // @flow |
报错如下:
1 | index.js:6 |
确认对象包含所有它应该包含的属性。
代码如下:
1 | // @flow |
报错如下:
1 | index.js:9 |
确认不存在的对象属性不被访问。
代码如下:
1 | // @flow |
报错如下:
1 | index.js:9 |
更深入的探讨
还有一些常见的好处,我可能忘了。但上面的例子已经涵盖了绝大部分。如果你在想,“就只有这些吗?”,那么还可以进行更加深入的研究。
Giulio Canti 写了相当多的关于 Flow 更高级的东西,而不仅仅是使用 Flow 来限制变量类型、参数类型以及返回类型。
- Phantom Types with Flow这篇文章中介绍了你可以使用;类型来检测用户的输入是否合法。
- Refinement Types with Flow这篇文章中谈到你可以创建具有内置约束的类型。
- Higher Kinded Types with Flow这篇文章中谈到你可以创建更高层级的类型。
- The Eff Monad Implemented in Flow这篇文章中谈到您可以将代码的副作用(译者注:side effects,“副作用”一词请参考https://www.zhihu.com/question/30779564)表示为类型。
他还撰写了flow-static-land,这是很让人兴奋的。
长话短说的总结
- JavaScript 既是弱类型语言又是动态类型语言,极其容易出错,也是它成为糟糕语言的一个重要原因。
- 由于前期成本很低,并且具有缓慢演进的能力,Flow 通过向 JavaScript 添加类型系统来解决这两个问题。