Ruby的静态类型:大规模采用冰糕

2020-11-21 21:33:45

加入我们2020年11月25日,美国东部时间下午1点参加ShipIt!礼物:当我们谈论Shopify的静态类型时,Shopify的Ruby静态类型的现状。我们将分享为什么我们选择Sorbet作为整体,以及在此过程中所学到的教训。请注册。

Shopify变化很多。我们每天将约400个提交合并到主分支,并每天部署40次新版本的核心整体。 Monolith也很大:37,000个Ruby文件,622,000个方法,超过2,000,000个调用。即使使用最严格的审核流程和超过150,000项自动测试,这种具有动态语言的规模仍然是确保一切顺利运行的挑战。开发人员将从短的反馈循环中受益,以确保我们的整体对商户的稳定性。

自2018年以来,我们的Ruby Infrastructure团队一直在研究使Ruby开发人员更安全,更快,更愉快的开发过程的方法。尽管Ruby与其他语言不同,并带来了使Shopify成为今天的惊人功能,但我们认为其他语言缺少一个功能:静态类型。

即使在2018年,为Ruby打字也不是一件新鲜事。为了将类型注释直接集成到语言中或通过外部工具(RDL,Steep)或作为库(干类型)进行了一些尝试。

考虑到其代码库和文化,哪种解决方案最适合Shopify?对于Ruby Infrastructure团队来说,最适合打字解决方案的需求是:

渐进式打字:键入整体不是一件容易的事,一天之内也无法完成。我们的代码发展很快,我们不能要求开发人员在向现有代码库添加类型时停止编码。我们需要灵活地添加类型,而又不会阻碍开发过程或限制我们满足商家需求的能力。

速度:考虑到Shopify代码库的大小,速度是一个问题。如果我们的目标是提供错误的快速反馈并消除持续集成(CI)带来的压力,那么我们需要一个快速的解决方案。

全面的Ruby支持:我们在Shopify使用所有Ruby。我们的文化包含语言,并从所有功能中受益,甚至包括元编程,重载和类重新打开等难以键入的功能。支持Rails也是必须的。从代码优雅到开发人员满意,选择的解决方案需要与所有Ruby功能尽可能兼容。

有了这样的要求列表,当时所有竞争者都无法满足我们的需求,尤其是速度要求。我们开始考虑开发自己的解决方案,但是与正致力于解决问题的Stripe召开的恰到好处的会议向我们介绍了Sorbet。

Sorbet当时是开放源代码,并且正在大力发展中,但已经很有希望。它是为具有出色性能的渐进式打字而设计的(每个核心每秒可以分析100,000行),比运行自动化测试快得多。由于有了Ruby接口文件(RBI),它可以处理诸如元编程之类的难以键入的事情。这就是Shopify在2019年初开始迈向Ruby静态类型检查的过程。

仅由三人组成的团队,我们的工作量很大,因此使用Shopify的Get Shit Done(GSD)框架完全键入我们的整体语言将是一种方法。

我们只键入了几个文件,就可以在核心整体上测试Sorbet的可行性,以检查是否可以从中受益,同时又不损害其他开发人员的工作。 Sorbets的渐进方法被证明是行之有效的。

我们手动创建了RBI文件来表示Sorbet无法理解的内容。我们检查了我们是否支持Ruby的最高级功能以及Rails构造。

我们在关注性能的同时添加了越来越多的文件,以确保Sorbet能够随我们的整体扩展。

这使我们充满信心,Sorbet是解决我们问题的正确选择。一旦我们正式决定选择Sorbet,我们就会反思如何在整体架构中实现100%的采用率。为了确定路线图,我们查看了:

Sorbet中的类型检查具有不同的严格性级别。严格性是在每个文件的基础上定义的,方法是在文件中添加称为sigil的魔术注释,写为#类型:LEVEL,其中LEVEL可以是以下值之一:

忽略:在此级别上,Sorbet甚至不会读取该文件,并且根本不会报告任何错误。

false:仅报告与语法,信号的恒定分辨率和正确性有关的错误。在此级别上,sorbet不会检查文件中的调用,即使所调用的方法在代码库中不存在也是如此。

是:这是Sorbet实际开始键入检查代码的级别。所有调用的方法都必须存在于代码库中。对于每个调用,Sorbet将检查参数计数是否与方法定义匹配。如果该方法具有签名,Sorbet还将检查其类型。

严格:在此级别上,所有方法都必须具有签名,并且所有常量和实例变量必须具有显式注释的类型。

强:冰糕不再允许使用无类型变量。实际上,此级别实际上对于大多数文件都不可用,因为Sorbet尚不能输入所有内容,甚至Stripe也建议不要使用它。

一旦我们能够在我们的代码库上运行Sorbet,我们就需要一种跟踪我们的进度并确定键入整体的哪些部分具有哪些严格性或哪些部分需要更多工作的方法。为此,我们创建了SorbetMetrics,该工具能够收集并显示有关键入所有内部项目的覆盖率的度量。我们开始跟踪三个衡量Sorbet采用率的关键指标:

每天,SorbetMetrics都会使用Sorbet提取最新版本的Monolith和其他Shopify项目,计算这些指标并将其显示在内部供所有开发人员使用的仪表板中。

如果我们将打字视为一种产品,那么我们还需要关注支持和启用Shopify开发人员的“客户”。我们的目标之一是建立一个强大的支持系统,以解决出现的所有问题并降低开发人员的速度。

最初,我们通过专用的Slack渠道为开发人员提供支持,Shopifolk可以在其中向团队提出问题。我们会实时回答这些问题,并在需要我们提供重要信息的打字方面帮助Shopifolk。

这种白手套支持模型显然无法扩展,但是对于我们的团队而言,这是一个绝佳的学习机会-我们现在了解了最大的挑战和反复出现的问题。我们最终一遍又一遍地解决了一些问题,但它巩固了理解模式并决定接下来要使用的功能的工作。

使用Slack意味着我们的答案永远不会被发现。我们将大部分支持和对话转移到我们的内部Discourse平台上,从而提高了可发现性并扩大了知识共享范围。这也使我们可以在一个地方记录解决方案,并让开发人员尽可能地自助。随着我们与Sorbet一起开展越来越多的项目,此解决方案的扩展性更好。

为了进一步解除对用户的封锁,我们还需要确保他们的幸福。如果Sorbet以及更普遍的Ruby中的静态类型使我们的开发人员痛苦不堪,那么它就不适合我们。我们知道,这样做会带来更多工作,因此,需要在带来好处和带来不便之间取得平衡。

我们用来衡量开发人员对Sorbet意见的第一个工具是调查。一年两次,我们向所有开发人员发送“ Typing @ Shopify”调查,并收集他们对Sorbet的好处和局限性以及未来我们应该关注的方面的看法。

我们使用简单的问题(“是”或“否”,或“完全不同意”(1)到“完全同意”(5)量表),然后研究答案随着时间的变化。调查结果给了我们有趣的见解:

我们的主要观察结果是,随着打字覆盖率的提高,开发人员更喜欢Sorbet。这是促使我们提高输入类型的文件的100%的动力的原因之一:真正实现并最大化带有签名的方法的数量。

第二个工具是对个人开发人员的采访。我们选择了一个与Sorbet合作的团队,并会见每个成员,讨论他们在整体或其他项目中使用Sorbet的经验。我们对他们的好恶有更好的了解,我们应该改进什么,以及在介绍Sorbet时如何更好地支持他们,因此团队将Sorbet保留在他们的项目中。

当前,仅在我们的主要整体上执行Sorbet,并且我们还有大约60个其他内部项目也选择使用Sorbet。在我们的主要整体文件上,我们要求至少所有文件都键入:false和Sorbet在每个PR的连续集成平台(CI)上运行,并且如果发现类型检查错误,则构建失败。我们目前正在评估在运行自动化测试之前对CI强制执行有效类型检查的想法。

截止到今天,我们80%的文件(包括测试)都已输入:真或更高。我们几乎有一半的调用是键入的,而我们的一半方法是带有签名的。所有这些都可以在我们的开发人员机器上15秒内进行类型检查。

圆形图显示了我们整体的哪些部分处于哪个严格度级别。每个点代表一个Ruby文件(测试除外)。每个圆圈代表一个组件(一组针对相同应用程序关注的Ruby文件)。是的,它看起来像培养皿,我们的目标是消灭坏的橙色未分型细胞。

在核心整体之外,我们还观察到使用Sorbet的Shopify项目(包括内部和开源项目)自然增加。在我编写这些行时,现在有60多个项目在使用Sorbet。 Shopifolks喜欢Sorbet,可以自己使用它,而不必强迫这样做。

最后,我们跟踪开发人员运行命令dev tc的次数,以在其开发计算机上使用Sorbet对项目进行类型检查。这说明我们开发人员无需等待CI使用Sorbet,每个人都可以享受更快的反馈循环。

现在,Sorbet已在我们的核心整体以及许多内部项目中被完全采用,我们开始在我们的代码库和开发人员中看到它的好处。我们致力于采用Sorbet的团队是更广泛的Ruby Infrastructure团队的一部分,该团队负责为Shopify提供快速,可扩展和安全的Ruby语言。作为该任务的一部分,我们认为静态类型可以为Ruby开发人员提供很多功能,尤其是在处理大型,复杂的代码库时。

在这篇文章中,我重点介绍了我们遵循的过程,以确保Sorbet是满足我们需求的正确解决方案,将静态打字视为产品,并向客户展示了该产品的优势:Shopifolk在我们的整体和外部上工作。您是否想知道我们如何到达那里?然后您将对第二部分感兴趣:大规模采用Sorbet,在此我介绍为简化和更快地采用而构建的工具,我们开源共享给社区的项目以及静态类型实验的初步结果减少生产错误。

Shopify变化很多。我们每天将约400个提交合并到主分支,并每天部署40次新版本的核心整体。 Monolith也很大:37,000个Ruby文件,622,000个方法,超过2,000,000个调用。即使使用最严格的审核流程和超过150,000项自动测试,这种具有动态语言的规模仍然是确保一切正常运行的挑战。开发人员将从短的反馈循环中受益,以确保我们的整体对商户的稳定性。自2018年以来,我们的Ruby Infrastructure团队一直在研究使Ruby开发人员更安全,更快,更愉快的开发过程的方法。虽然Ruby与其他语言不同,并带来了使Shopify成为今天的惊人功能,但我们认为其他语言缺少一个功能:静态类型。在此活动中,您将学到

无论您身在何处,您的下一个旅程都将从这里开始!如果您从头开始构建解决现实问题的系统,您会感兴趣吗?访问我们的工程职业页面以了解我们的空缺职位并了解默认情况下的Digital。

来自构建和扩展Shopify的团队的故事,Shopify是领先的基于云的多渠道商业平台,为全球超过1,000,000家企业提供支持。