6

TypeScript 被吹过头了?

 4 years ago
source link: https://www.infoq.cn/article/hONCcNMIDZiVqzpxIK17
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

开始看本文之前,我希望读者朋友们知道我在很大程度上是一位 TypeScript 粉丝。在我的前端 React 项目和各种后端 Node 工作里,所使用的主要编程语言都是 TypeScript。我是这条船上的人,但也确实有一些疑惑,想在这篇文章中讨论一下。到目前为止,我已经使用 TypeScript 写了至少三年的代码,涉及的项目不计其数,因此可以说 TypeScript 的确是走在了正路上,或者说满足了某种需求。

TypeScript 克服了一些难以逾越的障碍,成为了前端编程领域的主流之选。TypeScript 在 这篇文章 列出的最受欢迎编程语言中排名第七。

无论你是否在使用 TypeScript,不管多大规模的软件团队都应遵循以下实践:

  • 精心编写的单元测试应在合理范围内覆盖尽可能多的生产代码;
  • 结对编程——另一双眼睛可以捕捉到的远不止语法错误;
  • 良好的同行评审流程——正确的同行评审可以找出许多机器无法捕获的错误;
  • 使用 eslint 之类的 linter。

TypeScript 可以在这些基础之上增加额外的一层安全性,但我认为这在编程语言的需求列表中是排在非常靠后的位置上的。

TypeScript 并非健全的类型系统

我认为这可能是 TypeScript 当前版本面临的主要问题,但首先,我来定义一下什么是健全(sound)和不健全(unsound)的类型系统。

健全

一个健全的类型系统能确保你的程序不会进入无效状态。例如,如果一个表达式的静态类型是 string,则在运行时,对它求值时你肯定只会得到一个 string。

在健全的类型系统中,永远不会在编译时 或运行时 出现表达式与预期类型不匹配的情况。

当然, 健全性(soundness) 也是有级别的,具体含义可以斟酌。TypeScript 具有一定程度的健全性,并会捕获以下类型的错误:

复制代码

// 类型'string'不能赋值给 类型'number'
constincrement = (i:number):number=> {returni +"1"; }

// 类型'"98765432"'的参数不能赋值给类型'number'的参数.
constcountdown:number= increment("98765432");

不健全

关于 Typescript,以下事实是非常明确的:100%的健全性不是它的目标,并且 TypeScript 的 非目标列表 中的 3 号非目标明确指出:

应用一个健全或“正确无误”的类型系统(不是我们的目标)。相反,要在正确性和生产力之间取得平衡。

这意味着不能保证变量在运行时具有定义的类型。我可以用下面一些人为的例子说明这一点:

复制代码

interfaceA {
x:number;
}

leta: A = {x:3}
letb: {x:number|string} = a;
b.x ="unsound";
letx:number= a.x;// 不健全

a.x.toFixed(0);// 这啥?

上面的代码是不正确的,因为从 A 接口中可知 a.x 应该是一个数字。不幸的是,经过一些重新赋值后,它最终以一个字符串的形式出现,并且后面的代码能通过编译,但会在运行时出错。很不幸,这里显示的表达式可以正确编译:

复制代码

a.x.toFixed(0);

我认为这可能是 TypeScript 的最大问题,那就是健全性不是它的目标。我仍会遇到许多运行时错误,这些错误没有被 tsc 编译器标记出来,但如果 TypeScript 有一个健全的系统就能做到了。采用这种方法,意味着 TypeScript 在健全和不健全的阵营之间是在脚踩两只船。这种摇摆路线遇上 any 类型就更严重了,我将在后文提到这一点。我还是得编写尽可能多的测试,这让我感到沮丧不已。当我第一次开始使用 TypeScript 时,我错误地得出了一个结论,以为以后就用不着编写这么多单元测试了。

TypeScript 挑战了现状,声称降低用户使用类型的认知开销比类型健全更重要。

我理解为什么 TypesScript 会走这条路,并且有一个观点指出,如果类型系统的健全性得到 100%保证,那么 TypeScript 的采用率就不会那么高了。随着 dart 语言逐渐流行(因为 Flutter 现已广泛应用),这个看法也被证伪了。健全性是 dart 语言的一个目标,可以参考 这里的讨论

TypeScript 的不健全性,以及它在严格的类型系统上开的一大堆天窗拖累了它的效率,让它在今天处于相当 鸡肋 的位置上,非常可惜。我的愿望是,随着 TypeScript 的流行,会有更多的编译器选项可供使用,从而使高级用户可以争取 100%的健全性。

TypeScript 不保证任何运行时类型检查

运行时类型检查不是 TypeScript 的目标之一,因此这种愿望可能永远不会实现。例如,在处理从 API 调用返回的 JSON 负载时,运行时类型检查会很有用。如果我们可以在类型级别上控制它,就用不着一整筐错误和许多单元测试了。

我们无法在运行时保证任何事情,因此可能会出现以下情况:

复制代码

constgetFullName =async():string=>{
constperson: AxiosResponse =awaitapi();

//response.name.fullName 可能在运行时成为 undefined
returnresponse.name.fullName
}

有一些支持这种需求的库,例如 io-ts;虽然它很棒,但可能意味着你必须复制你的模型。

可怕的 any 类型和严格选项

any 类型就是字面意思,编译器允许任何操作或赋值。

TypeScript 往往在小事上表现很不错,但是人们习惯给花费时间超过 1 分钟的任何事物都来个 any 类型。我最近在一个 Angular 项目中工作,看到很多这样的代码:

复制代码

exportclassPerson {
public_id:any;
publicname:any;
publicicon:any;

TypeScript 会让你忘掉类型系统。你可以用一个 any 把所有事物的类型干掉:

复制代码

("oh my goodness"asany).ToFixed(1);// 记得我提到健全性时说的话吗?

strict 编译器选项会启用以下编译器设置,这些设置会让事物看起来更健全:

  • –strictNullChecks
  • –noImplicitAny
  • –noImplicitThis
  • –alwaysStrict

还有 eslint 规则 @typescript-eslint/no-explicit-any。

any 的扩散会毁掉你类型系统的健全性。

结论

我必须重申一点,我是 TypeScript 粉丝,也在日常工作中使用它,但我确实认为它有很多缺陷,而且炒作有些过头了。Airbnb 声称 TypeScript 可以阻止 38%的错误。我非常怀疑这一精确的百分比数字。TypeScript 不会给已有的良好实践锦上添花。我还是得编写尽可能多的测试。你可能不相信,我编写的代码变多了,可能还得编写许多类型测试。我仍然会遇到意外的运行时错误。

TypeScript 提供了比较基础的类型检查,但健全性和运行时类型检查不是它的目标,这让 TypeScript 处于脚踩两条船的状态,一只脚踏上了更好的那艘,一只脚还呆在现在的破船上,真是不幸。

TypeScript 的亮点在于良好的 IDE 支持,比如说 vscode 里如果我们打错什么内容,就会获得视觉反馈。

mMj6nmN.jpg!web

vscode 中的 TypeScript 错误

使用 TypeScript 还可以增强重构能力,并且在对修改后的代码运行 TypeScript 编译器时,可以立即识别出代码中断(例如方法签名的更改)。

TypeScript 带来了良好的类型检查,并且绝对比没有类型检查器或仅使用普通的 eslint 要更好,但是我认为它能更进一步。另外对于我们这种想要更多能力的用户来说,它应该可以提供足够多的编译器选项才是。

英文原文

Is TypeScript Worth It?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK