Talks

Sorbet: Practical Gradual Type Checking For Ruby

Paris.rb Conf 2020

00:00:14.200 Also, telephone system plays. Yup, that plays. Si, merci beaucoup. Perelman vitae. I hope that my accent wasn't too awful.
00:00:19.099 My name is Getty. I work at Stripe on the Developer Productivity Team, and in particular on something called Sorbet, which you may or may not have heard about. You've probably heard of Stripe, if for no other reason than it came up yesterday as an example of a breaking API.
00:00:27.020 Stripe is a company that builds businesses on the internet. Our bread and butter is payments, but we also have various other products for managing subscriptions and conducting business analytics. We are a very large company—well, very large in terms of our reach. We have more than 2000 employees spread across the world, with ten major hubs, including San Francisco, Seattle, Dublin, and Singapore. I didn't write it up here, but Paris is one of them, and we are also very remote-friendly.
00:00:45.860 I am myself a remote employee; I work out of Oregon, where we do not have a Stripe office. I also mentioned that I'm part of the Developer Productivity Team. So, what does that mean? Developer productivity at Stripe is about enabling our product engineers to focus on being product engineers. Anything that can help them go from writing a line of code to building a product is under the purview of developer productivity.
00:01:19.729 This can involve building tooling or deploying existing tools. We are responsible for systems like RoboCop, which can be our CI system. Sometimes, this might even involve digging into the code and building abstractions that make it easier for others—not just necessarily creating tools, but actually going in and improving libraries and similar resources.
00:01:57.649 Stripe is also a major user of Ruby. We have millions of lines of Ruby code; it is our primary language for building anything product-related. While we have a handful of other languages—some Python, some Scala, some Go—if you are building something product-focused, you are most likely using Ruby.
00:02:10.670 We have hundreds of engineers working on the Ruby codebase at any given time, and thousands of changes to our Ruby monorepo, which contains about ten macro services. This is a lot of Ruby. Something that we in developer productivity do is run surveys about twice a year. We ask people what we are doing well, what we are doing poorly, and so on.
00:02:39.610 One of the questions we ask consistently is about their biggest pain points in writing Ruby. We distilled the feedback down to three main points. First, it takes too long to get feedback between writing a line of code and knowing whether that line of code is actually doing what it's supposed to do. There can be a pretty long turnaround time. Second, it is often difficult to grasp unfamiliar code, which there is a lot of, given we have millions of lines of Ruby.
00:03:24.910 Third, it is too easy to accidentally break things. I’ll dive into these points later, but we started thinking about how to address these issues. We considered a handful of options. One option is doing nothing—it's always a tempting strategy. I'm a big fan of it in my personal and professional life.
00:03:58.800 However, doing nothing has its problems, like productivity suffering, which is kind of anathema to our whole team’s point. We could treat symptoms of code as they come up, but that's going to be difficult because new code will still suffer from the same problems. We could do rewrites, but rewriting everything will require hundreds of engineers. It could end up feeling like an all-or-nothing project, which is the kind of initiative that often breaks companies.
00:04:59.470 We talked about writing a type checker. Many of the issues we've described are things that statically typed languages handle relatively well—for instance, refactoring and getting familiar with APIs since they have contracts. One of the best parts about this is it’s a relatively low-risk project. We could test building it out as a small experiment with three engineers over a couple of months.
00:05:16.820 If it turns out to be terrible, we've only wasted three engineers' time for a couple of months. A full rewrite could have much worse consequences. So, we went with a type checker, and that project turned out to be what we now call Sorbet. To give you a rough timeline, the project officially kicked off in October 2017.
00:06:14.720 By February of 2018, we had the first type code in our monorepo—not just example code, but actual code that was typed. Initially, we did a dark rollout, meaning we did not tell anyone we were doing this. We, as engineers on the Sorbet team, quietly added type signatures to the code. The errors that resulted from that were reported to us silently; the team would then iterate and add new type signatures.
00:06:53.100 Eventually, we exposed it as a tool to programmers. It took off; not everyone loved it at first. One of the appealing things is that you can adopt it gradually. By June 2018, it became a mandatory part of our CI system. For any code to be merged into master, it needed to type-check with Sorbet. There were many other developments during that period.
00:07:36.030 I was hired in that time, which was a nice benefit for me, but a lot of other great things happened for Sorbet, too. In June 2019, we open-sourced it after a long closed beta involving about 30 or 40 companies, including big names like Shopify and Kickstarter. We sought to ensure it would be a pleasant experience from the start. As of now, it has been open-source for six months, and anyone can use it.
00:08:53.990 Let's revisit the problems I outlined earlier, the three fundamental problems we hoped Sorbet would solve. The first issue is that it takes too long to grasp unfamiliar code. This is a simplified example from our codebase. If you approach certain snippets without context, you might have a number of questions about the meaning and implications of the code.
00:09:47.930 For instance, with the `merchants` variable, is that a merchant object we've retrieved from the database, or is it a key we're using to reference it? Understanding can take time. Similarly, if you encounter a piece of code you think should be referencing something, you may find yourself wondering about its necessity or about how the surrounding methods interact with each other.
00:10:51.290 The second problem is that it takes too long to receive feedback. At Stripe, we have a lot of tests because we move a lot of money. Our industry demands it. Running tests on a local laptop can take literal days. We've worked hard to optimize our CI processes for speed, but it could still take five to twenty minutes for a run.
00:11:43.770 On the flip side, when programming, one keystroke is about 50 milliseconds. What we would love is for Sorbet to provide feedback within that 50 millisecond time frame instead of the lengthy five to twenty minutes typical for CI.
00:12:29.560 The third issue, reflected in our code example, is that it’s too easy to inadvertently break things. In a monorepo like ours, making changes to an API often involves altering every piece of code that uses it. Knowing how to complete a specific transformation seamlessly can be tricky.
00:13:13.480 Keeping track of edge cases, particularly in finance—where even small changes can lead to significant ramifications—poses a constant challenge. While we strive to have a thorough testing framework, tests inevitably come with limitations. The fundamental question remains: does the tool we've built actually address these concerns?
00:13:58.670 To demonstrate, I will be using the tool itself. This is an actual implementation of the editor we use with Sorbet. We leverage Visual Studio Code, and I'll delve deeper into that later. Sorbet benefits from a language server implementation that enhances its data-driven capabilities.
00:14:55.400 When trying to understand unfamiliar code, Sorbet assists by highlighting the associated type signature. If I mouse over a method, it offers the starting point I need—clarifying exactly where it’s defined, what arguments it requires, and its return type. Sorbet also tracks whether something is nil or non-nil, allowing it to flag issues when something is wrongly classified.
00:16:06.500 Returning to the second point regarding receiving quick feedback: if I change this `merchant` variable—a string—to a number, I will immediately see red squiggles indicating a problem. Hovering over the issue reveals an expected type mismatch—indicating the system’s value in preventing error propagation.
00:16:47.500 Lastly, on avoiding the fear of refactoring, adding a type signature to a method can ensure a smoother long-term maintenance experience. Sorbet even has autocomplete that can infer potential type signatures, allowing for quick updates. This tool provides clarity on relationships between various pieces of code across files.
00:17:57.819 We believe Sorbet has effectively addressed many of the concerns we've outlined. Deploying Sorbet in our codebase has enabled us to feel significantly more confident about our processes. That being said, we continue to work on enhancing Sorbet, with performance being a top priority.
00:18:54.360 Ruby 3 is slated to include type signatures for the standard library, which we plan to support with Sorbet. As the open-source community surrounding this tool grows, we see many new contributors helping improve Sorbet.
00:19:27.329 We’ve had various projects emerge—one including a tool for using Sorbet with Rails, alongside other tools that can aid in creating Sorbet-compatible type signatures from YARD annotations.
00:20:09.120 Feedback from users has been overwhelmingly positive. Many have commented on the significant improvements in productivity they've experienced since adopting Sorbet.
00:20:47.979 Looking to the future, we aim to make Sorbet faster. Internal tests show that while runtime type checking is valuable, we want to keep overhead minimal in production. Some users prefer to disable runtime checks while keeping it enabled for tests, which we support as well.
00:21:57.590 We recognize the importance of type checking for new projects and legacy code alike, and we are dedicated to improving our tooling continuously. Users can explore Sorbet today at sorbet.run, where they can experience enhanced feedback and many of the benefits we've discussed.
00:22:30.760 Thank you for your time! Does anyone have any questions?
00:23:11.730 [Discussion about Ruby usage at Stripe and features planned for Sorbet.]
00:25:52.390 [Further questions about runtime overhead and functionality of Sorbet during production.]