类型或测试:为什么不呢?
关于类型化JavaScript的价值,时不时就会出现一场辩论。“多写些测试!”一些反对者喊道。“用类型替换单元测试!”其他人大声疾呼。两者在某些方面是对的,在另一些方面是错的。Twitter的空间不足以体现细微之处。但在本文中,我们可以尝试阐述一个合理的论点,说明两者如何以及应该如何共存。
正确性:我们真正想要的
最好从结果开始。我们从所有这些元工程最终真正想要的是正确性。我不是指严格的理论计算机科学定义,而是程序行为与其规范的更普遍的遵守:我们脑海中对程序的工作方式有一个想法,而编程过程则组织位和字节,使这个想法成为现实。因为我们并不总是精确地知道我们想要什么,而且我们希望确信我们的程序在进行更改时不会中断,所以我们在现有的原始代码之上编写类型和测试,仅仅是为了让事情首先能够工作。
因此,如果我们接受正确性是我们想要的,而类型和测试只是实现这一目标的自动化方法,那么最好有一个直观的模型来展示类型和测试如何帮助我们实现正确性,从而理解它们在哪里重叠以及在哪里相互补充。
程序正确性的可视化模型
如果我们将程序可能执行的所有操作(包括错误)的整个无限图灵完备可能空间想象为一个巨大的灰色区域,那么我们希望程序执行的操作,我们的规范,是该可能空间的一个非常非常小的子集(下图中的绿色菱形,为了显示某些内容而夸大了大小):
我们在编程中的工作是尽可能地将我们的程序与规范协调一致(当然,我们知道我们是不完美的,我们的规范不断变化,例如由于人为错误、新功能或未指定的行为;因此我们从未完全设法实现完全重叠):
再次注意,为了我们在此处的讨论目的,程序行为的边界也包括计划中和计划外的错误。我们对“正确性”的含义包括计划中的错误,但不包括计划外的错误。
测试与正确性
我们编写测试以确保我们的程序符合我们的期望,但有许多需要测试的内容的选择:
理想的测试是图中的橙色点——它们准确地测试了我们的程序是否与规范重叠。在这个可视化中,我们并没有真正区分测试类型,但您可以将单元测试想象成非常小的点,而集成/端到端测试是大的点。无论哪种方式,它们都是点,因为没有一个测试可以完全描述程序中的每条路径。(事实上,您可以拥有100%的代码覆盖率,但仍然无法测试每条路径,因为存在组合爆炸!)
图中的蓝色点是一个糟糕的测试。当然,它测试了我们的程序是否有效,但它实际上并没有将其固定到底层规范(归根结底,我们真正想要从程序中获得的东西)。当我们修复程序以更紧密地与规范对齐时,此测试就会中断,给我们一个误报。
紫色点是一个有价值的测试,因为它测试了我们认为程序应该如何工作,并确定了程序当前没有工作的区域。领先使用紫色测试并相应地修复程序实现也被称为测试驱动开发。
图中的红色测试是一个罕见的测试。它不是测试“快乐路径”(包括计划的错误状态)的普通(橙色)测试,而是一个期望并验证“不快乐路径”失败的测试。如果此测试在应该“失败”的地方“通过”,则这是一个巨大的早期警告信号,表明出了问题——但编写足够的测试来覆盖规范绿色区域之外存在的巨大可能的不快乐路径基本上是不可能的。人们很少发现测试那些不应该工作的东西不工作有价值,所以他们不做;但当事情出错时,它仍然可以是一个有帮助的早期警告信号。
类型与正确性
如果测试是程序可能执行操作的可能性空间上的单个点,则类型表示从总可能性空间中分割整个部分的类别。我们可以将它们可视化为矩形:
我们选择一个矩形来对比表示程序的菱形,因为没有一个类型系统本身可以使用类型来完全描述我们的程序行为。(要举一个简单的例子,一个应该始终为正整数的id是一个数字类型,但数字类型也接受分数和负数。除了非常简单的数字文字联合之外,无法将数字类型限制在特定范围内。)
类型在您编写代码时充当程序可以运行位置的约束。如果我们的程序开始超过程序类型的指定边界,我们的类型检查器(如TypeScript或Flow)将简单地拒绝让我们编译程序。这很好,因为在像JavaScript这样的动态语言中,很容易意外地创建一个肯定不是您想要创建的崩溃程序。最简单的附加值是自动空值检查。如果foo没有名为bar的方法,则调用foo.bar()将导致众所周知的undefined is not a function运行时异常。如果foo被完全类型化,则可以在编写时由类型检查器捕获此问题,并具体指出有问题的代码行(并具有自动完成的伴随好处)。这是测试根本无法做到的事情。
我们可能希望为我们的程序编写严格的类型,就好像我们试图编写仍然符合我们规范的尽可能小的矩形一样。但是,这有一个学习曲线,因为充分利用类型系统涉及学习全新的语法和运算符以及泛型类型逻辑的语法,这些语法和逻辑是模拟JavaScript的完整动态范围所必需的。手册和备忘单有助于降低学习曲线,并且这里需要更多投资。
幸运的是,这个采用/学习曲线不必阻止我们。由于类型检查是Flow的可选过程,并且TypeScript的可配置严格性(能够选择性地忽略有问题的代码行),我们可以从类型安全的范围内进行选择。我们甚至可以对此进行建模:
较大的矩形,如上图中的大红色矩形,表示对代码库的类型系统的非常宽松的采用——例如,允许implicitAny并完全依赖类型推断来仅仅将我们的程序从最糟糕的编码中限制出来。
中等严格性(如中等大小的绿色矩形)可以表示更忠实的类型化,但有很多漏洞,例如在整个代码库中使用显式any实例和手动类型断言。尽管如此,即使进行这种轻量级的类型化工作,与我们的规范不匹配的有效程序的可能表面积也会大大减少。
最大严格性,如紫色矩形,使事情与我们的规范非常紧密,以至于它有时会发现您的程序中不符合的部分(这些通常是程序行为中计划外的错误)。像这样在现有程序中查找错误是将普通JavaScript代码库转换的团队中非常常见的案例。但是,从我们的类型检查器中获得最大的类型安全性可能需要利用泛型类型和特殊运算符,这些运算符旨在为每个变量和函数细化和缩小可能的类型空间。
请注意,我们不必在编写类型之前先编写程序。毕竟,我们只是希望我们的类型能够密切模拟我们的规范,因此我们实际上可以先编写我们的类型,然后稍后再填充实现。理论上,这将是类型驱动开发;在实践中,很少有人真正以这种方式开发,因为类型与我们的实际程序代码紧密地渗透和交织在一起。
将它们放在一起
我们最终要建立的是一个直观的可视化,说明类型和测试如何在保证程序的正确性方面相互补充。
我们的测试断言我们的程序在选定的关键路径中专门按预期执行(尽管如上所述,存在某些其他测试变体,但绝大多数测试都这样做)。在我们已经开发的可视化语言中,它们将程序的深绿色菱形“固定”到规范的浅绿色菱形上。程序的任何移动都会破坏这些测试,这会使它们发出警报。这很棒!测试也具有无限的灵活性和可配置性,适用于最自定义的用例。
我们的类型断言我们的程序不会脱离我们的控制,方法是禁止超出我们绘制的边界的可能故障模式,希望尽可能紧密地围绕我们的规范。在我们可视化语言中,它们“包含”程序偏离规范的可能漂移(因为我们总是存在缺陷,我们犯的每一个错误都会为我们的程序增加额外的失败行为)。类型也是钝的,但功能强大的(因为类型推断和编辑器工具)工具,受益于强大的社区提供的您不必从头开始编写的类型。
简而言之:
- 测试最擅长确保快乐路径有效。
- 类型最擅长防止不快乐路径存在。
根据它们的优势将它们一起使用,以获得最佳结果!
如果您想了解更多关于类型和测试如何交叉的信息,Gary Bernhardt关于边界的精彩演讲和Kent C. Dodds的测试奖杯对我的这篇文章的思考产生了重大影响。
以上是类型或测试:为什么不呢?的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

在本周的平台新闻综述中,Chrome引入了一个用于加载的新属性,Web开发人员的可访问性规范以及BBC Move

Goofonts是由开发人员和设计师丈夫签名的附带项目,它们都是版式的忠实拥护者。我们一直在标记Google
