Ruby

State of Sorbet: A Type Checker for Ruby

We have developed a typesystem for Ruby at Stripe with a goal of helping developers understand code better, write code with more confidence, and detect+prevent significant classes of bugs.

This talk shares experience of Stripe successfully adopting Sorbet in our codebase which had millions lines of code that were written before the typechecker had been conceived. The talk will describe: - the process used to add typing to existing code; - many tools developed to support this process; - impact of this type system on safety and productivity at Stripe.

We also have some exciting announcements!
The talk does not require any previous knowledge of types and should be accessible to a broad audience.

RubyKaigi 2019 https://rubykaigi.org/2019/presentations/jez.html#apr19

RubyKaigi 2019

00:00:01.370 Hello, everyone! Alright, so you are here for the State of Sorbet, a type checker in Ruby. I am Paul Tarjan, and I have been at Stripe for about three years now on the Developer Productivity team.
00:00:07.649 Before that, I was at Facebook, working on HHVM and Hack, the PHP type checker. I did my master's at Stanford. Hi, I'm Jake! I've been at Stripe for about two years, most recently working on the Sorbet team. Before joining Stripe, I was still in school studying programming languages at Carnegie Mellon.
00:00:14.490 Alright, let's dive in. Today, we're going to talk about five things. I'm going to give a little bit of context about Stripe, discuss our Sorbet adoption, cover our editor integrations, and share some really exciting announcements. We'll also dive into some of the tooling we have been working on recently.
00:00:40.260 Hopefully, you've heard of us. If you haven't, we are a platform to accept payments worldwide. I recently updated this slide from last year, and it’s exciting to see the numbers go up over the year. We are now in 32 countries, millions of businesses use us, and we process billions of dollars annually. More than 80% of Americans used Stripe to buy something last year!
00:01:06.930 We're now up to 1,700 people with 10 offices worldwide. Our customers report a 59% increase in developer productivity when switching to Stripe. You can check out our careers page at stripe.com/jobs.
00:01:14.729 I am the technical lead for Developer Productivity. We are responsible for everything from the moment you type 'git branch' all the way until your code is in production.
00:01:17.080 If anything in between is not going great, we are there to help you and fix it. We deal with testing, code abstractions, developer environments, code review, language tools, documentation, and various language platforms. Essentially, the whole platform is what Developer Productivity does. We believe strongly in this mission.
00:01:34.840 The Sorbet type checker is just a small part of our larger mission to make Stripe engineers as productive as possible. Ruby is our primary programming language; all our product code is written in Ruby. This is an enforced subset of Ruby, and we follow many RubyCop rules, which we love.
00:01:47.980 We are not using Rails as our primary abstraction, which influences our type-checking capabilities. Most of our product code resides in a mono repo. This was an intentional structure rather than an accident; we use macroservices instead of small microservice architectures.
00:02:09.840 Currently, we have hundreds of engineers making thousands of commits every day, contributing to millions of lines of code. This is a substantial Ruby codebase to work with, and it sets the stage for discussing our type checker.
00:02:38.180 We are not doing this alone; we are very appreciative of all the help we have received along the way. Jeff Foster, a professor at Tufts University, has put a lot of work into our type checker for the last ten years, alongside many students.
00:02:46.750 We have also utilized standard library definitions that his team has already created to bootstrap Sorbet. Charlie Somerville at GitHub wrote a wonderful port of the White Cork parser into C++ for our implementation.
00:03:07.670 Recently, Sotero has been collaborating with us as well, and we appreciate this collaboration, especially with Mami-san on his tech profiler. We thank Max for initiating this whole project, and recently, three companies, Shopify, Coinbase, and Sourcegraph, have started contributing code back to Sorbet as they have been developing it in their environments.
00:03:30.590 Now, I am going to hand it over to Jake to discuss our process of adopting Sorbet here at Stripe.
00:03:44.490 Awesome! Thanks, Paul. I'm excited to talk about the process of adopting Sorbet at Stripe. At a high level, it can be broken down into three main phases. The first eight months of our project were spent building Sorbet from scratch. We began with no prior knowledge other than the desire to type-check Ruby. We iteratively implemented features into the type checker, ensuring each worked before moving to the next.
00:04:39.740 Last year at Ruby Kaigi, we announced Sorbet for the first time. Once we returned home, we focused on seeing how much of our codebase we could type. Our main goal was to identify and prevent specific classes of errors throughout the code. The first type of error we tackled was uninitialized constant errors.
00:05:20.500 For example, consider a code snippet where we have a class called Hello and a main method trying to instantiate it, but it has a typo: we are missing an 'L.' When we run this code in Ruby, it will produce an uninitialized constant error, suggesting we might have meant something else. Although Ruby provides useful feedback, we have to wait until running the code to get the error, which can take quite some time to discover when errors appear.
00:06:12.249 With Sorbet, we can run it on our project, and within seconds, it identifies that it couldn't resolve the constant and points directly to the line of code where the mistake occurred. After seven months of working to bring Sorbet to Stripe and typing as much code as we could, I’m thrilled to announce that we can now catch these errors in 100% of our Ruby files without executing any Ruby code!
00:06:50.390 This is an incredible improvement for speed feedback. We gain confidence in our code since we don’t need to make any changes to it to get this valuable feedback.
00:07:11.490 The next type of error we targeted was the undefined method error. We took a code snippet from before where we had added a method called greeting that returned a string. But instead of calling greeting, we mistakenly called greet. Again, Ruby will provide us with an excellent error message that tells us exactly what went wrong.
00:07:35.050 However, we need to run the code to receive this feedback. To leverage Sorbet better, we wanted to increase our confidence beyond what tests could offer us. By adding a single line at the top of the file that states 'typed: true,' Sorbet can now catch this error. If we run it, Sorbet will let us know that the method greet doesn't exist.
00:08:03.050 You might wonder whether we can achieve this in a large project like Stripe's codebase. After seven months with the team of three or four people working on adopting Sorbet at Stripe, I'm glad to report that we can catch this error in 80% of Ruby files by simply adding that one comment. The only question remains: why only 80%?
00:09:01.329 With 80% of the files properly annotated, we discovered that there are many bugs in our codebase, and we could only address so many with our small team. While we worked through the project over those seven months, we built tools to automate the fixing of these common errors, which allowed us to make significant progress.
00:09:45.749 While we focused on error detection, we also wanted Sorbet to provide insights beyond just errors. While developing code, we want to ask questions like: what types does this method accept, or could this method result in nil? For example, consider a method with a poorly named implementation.
00:10:12.329 We asked ourselves questions like whether it’s okay to call this method with a string or nil. Previously, we would need to dive into the code and understand it ourselves. With Sorbet, we can add a simple line at the top of the method, stating the argument it takes and what it returns. This way, it's straightforward to determine that it can take a string but not nil.
00:11:09.530 After those seven months adopting Sorbet, 62% of methods at Stripe now have a signature that helps us understand the code better.
00:11:42.750 To recap our numbers: we can now catch uninitialized constant errors in 100% of files, catch no method errors in 80% of files, and 62% of methods have signatures that allow us to grasp their functionality more deeply.
00:12:13.989 These numbers tell part of the story, but we wanted to hear from our users directly. We pulled quotes from Stripe engineers to show how Sorbet has impacted their workflows.
00:12:45.490 One engineer shared that he added strict typing to a package he rarely touches, and appreciated how useful it was for understanding it better. It demonstrates that beyond just being a tool that points out errors, Sorbet is being used to gain insights and explore unfamiliar code.
00:13:14.880 Another engineer from the billing team exclaimed that Sorbet's typed true was "the best thing since sliced bread!" He mentioned feeling like he is pair programming with the type checker, which illustrates that Sorbet enhances the development experience.
00:13:40.550 A few months ago, at a hackathon, one of our team members integrated Sorbet into his favorite editor. He mentioned he would pay significant money for this editor extension, which highlights the value of our tools.
00:14:24.740 In the last seven months, we learned many things about Sorbet, especially regarding speed. With millions of lines of code at Stripe, Sorbet type-checks our entire codebase in seconds. However, there were requests for even faster response times so that the editor responds instantaneously during coding.
00:15:17.740 To summarize, we learned that Sorbet is powerful enough to handle Ruby in a meaningful way, it is fast, but users desire it to be instant in their editors, and we managed to adopt it with a small team of three people by leveraging automated tools.
00:15:44.500 Next, I want to dive into the editor tools and open-source solutions we've been building for the community. Here’s a screenshot of VS Code at Stripe, showcasing how we use red squiggles to indicate a typo in a method. As soon as we correct the typo, the squiggle disappears.
00:16:19.670 We have implemented features that allow users to not only receive instant feedback on errors but also to navigate easily to where a method or variable is defined, utilizing Sorbet's knowledge of the codebase.
00:16:45.490 We’ve also included auto-completion and documentation features, which neatly display method inputs, outputs, and associated Yard documentation, making development smoother and more informative.
00:17:10.890 We invite everyone to try out these features at sorbet.run, where you can experiment with editor functionalities directly in the browser.
00:17:46.220 Since Sorbet was designed to be cross-platform, we partnered with Sourcegraph, a code search and navigation tool. They’ve integrated Sorbet capabilities into their product, allowing you to see types directly as you navigate through GitHub.
00:18:55.240 After we open-source Sorbet, we hope to see more integrations and tools. If you're building something that Sorbet could enhance, we would love to collaborate.
00:19:36.220 Now I'd like to hand it off to Paul for a couple of exciting announcements.
00:20:01.010 Thank you, Jake! I have four announcements to share today. First, as you probably heard yesterday at Matt's keynote, Ruby 3 and types are now friends. We've been closely collaborating with the core team in this area.
00:20:40.060 We are looking forward to the upcoming type definitions for the standard library and will eagerly adopt them in Sorbet when they become available. Second, we are open-sourcing soon!
00:21:11.110 We are currently in a private beta with three core companies collaborating with us and testing this tool. If you'd like to join this group, please email us at [email protected].
00:21:30.180 Our goal is to deliver a polished product that works straight out of the box. We want the experience to be seamless for everyone who tries it for the first time. We expect to begin opening the floodgates this summer, so stay tuned for announcements on the Stripe blog!
00:22:05.150 Number three: the Sorbet documentation is live! You can visit sorbet.org to see the documentation that Jake has been diligently working on for the past two months. Feel free to reach out to us with any questions or feedback.
00:22:40.580 Lastly, I want to discuss how our open-source tooling will function with Sorbet. You will have two gems: Sorbet for development purposes and Sorbet Runtime for your runtime type checking.
00:23:07.140 When you first start your project, you run 'srb init' to set everything up. It will create a Sorbet directory in your file system, which is meant for source control, containing a config file along with multiple tiers of files that provide type information.
00:23:45.840 The first tier focuses on gems that have community-provided type definitions, downloading as necessary to provide Sorbet with the types of methods in those gems. If the authors haven't submitted type signatures, we will serialize what we can find from object space.
00:24:45.230 We also handle definitions not explicitly exposed by metaprogramming through a hidden definition process, determining what methods exist in your codebase. Lastly, we include a to-do file listing constants we could not resolve, allowing developers to address those missing connections.
00:25:53.780 Additionally, we've created a repository called Sorbet-Typed for gem type definitions, available at github.com/sorbet/sorbet-typed. We welcome contributions of type annotations for your favorite gems!
00:26:30.990 We aim to improve how Sorbet understands DSLs. Our plugin system allows users to add plugins that can understand various DSL constructs.
00:27:14.610 For common DSLs like those found in Rails, we provide methods to understand and type-check them effectively, incorporating Ruby files that define how the DSL operates.
00:27:56.390 As for practical testing, I have tried it out on the default Rails blog application. A significant portion of files turned out to be type-checkable out of the box, which is a good sign.
00:28:32.350 When testing larger open-source projects, such as rubygems.org, we found that 58% of files were type-checkable with minimal effort. On the other hand, GitLab's maturation revealed that 99% of files could resolve constants, 19% of which Sorbet could check right away.
00:29:06.940 It only required seven manually written type signatures for Rails to cater to its type definitions throughout delivery. We are thrilled to leverage such adaptation for extensive use.
00:29:49.820 In conclusion, editor tooling remains a priority for us moving forward. If you'd like to join us in our private beta, please reach out to us at [email protected]. We’re excited about making Sorbet function smoothly with Rails and handling metaprogramming concerns effectively.
00:30:30.590 Lastly, we are asking everyone to check out the Sorbet documentation at sorbet.org. Thank you very much for your time! We look forward to connecting with you at the conference!
00:31:07.790 We have time for some questions. If folks would like to approach the microphones, please do so. English, if possible.
00:31:55.320 Yes, that's great. It's wonderful that you provide type checking for arguments, constants, and return types, but how about exceptions? They also require a level of dynamic analysis. Have you considered doing any checks for exceptions?
00:32:18.790 That’s an excellent question! The third member of our team, Dimitri, has worked on the Scala 3 compiler before joining us, and he often shares his views on this topic. For now, our focus is on maintaining the simplicity and effectiveness of Sorbet.
00:32:36.190 In fact, our most common constants errors arise from misspelled constants, like 'JSON' or others which can be easily corrected with the right checks.
00:32:52.590 Another audience member expressed excitement about the DSL support and asked whether plugins might clash since different libraries could have overlapping DSL methods.
00:33:11.990 Our current plugin system design only permits top-level method names to be utilized. If two libraries have methods with overlapping names, you will need to choose which plugin to run.
00:33:28.790 We're eager to improve that system, so if anyone has more ideas or suggestions, we’d love to hear them.
00:33:45.690 The audience also inquired about how Sorbet handles duck typing.
00:34:05.090 We recognize that a type checker’s primary role is to generate clear error messages. While Sorbet does support duck typing, we’ve opted for a nominative type system that requires naming interfaces for improved error clarity.
00:34:23.490 This allows us to anchor error messages to named interfaces rather than having generic descriptions that can be convoluted or confusing. For example, if you establish a module called 'Stringable,' it helps clarify what to expect.
00:34:54.790 Finally, an audience member expressed enthusiasm regarding the editor tooling and inquired if Sorbet is employing the Language Server Protocol.
00:35:12.090 Yes, it is indeed! The integration with the Language Server Protocol allows us to extend Sorbet features seamlessly to code editors. We utilized VS Code’s language server as the basis for some of our browser functionalities.