Slide 1

Slide 1 text

1

Slide 2

Slide 2 text

2 Seatbelts

Slide 3

Slide 3 text

3 Seatbelts save lives

Slide 4

Slide 4 text

Benefits 50% Reduction in serious injury 45% Reduction in risk of death 4 https://www.cdc.gov/motorvehiclesafety/seatbeltbrief/index.html

Slide 5

Slide 5 text

Yet... 4% of Canadian drivers 13% of US drivers 54% of Turkish drivers 5 drive without a seatbelt https://en.wikipedia.org/wiki/Seat_belt_use_rates_by_country

Slide 6

Slide 6 text

Why? 6 01 02 03 04 Find them uncomfortable Over 65s find them restricting Do not think they ever need them Think they’re less likely to have an accident https://www.bbc.com/news/blogs-magazine-monitor-29416372

Slide 7

Slide 7 text

“But cars are safe. We test them!” 7

Slide 8

Slide 8 text

- Costs too much ⏱ and - Cannot test every situation (Crash) Testing 8

Slide 9

Slide 9 text

The higher the stakes, the safer the car 9

Slide 10

Slide 10 text

10 Ufuk Kayserilioğlu Rails and Ruby Infrastructure Team

Slide 11

Slide 11 text

Shopify Scale Commerce 1M Merchants 8000 orders/min (peak) 12B USD sales/year 11 135B USD total sales

Slide 12

Slide 12 text

Shopify Scale Engineering 1500 RnD Employees ~21K Ruby files ~150K tests 12 ~40 deploys daily

Slide 13

Slide 13 text

13 There is too much at stake here. We need seatbelts!

Slide 14

Slide 14 text

Adopting Sorbet at Scale

Slide 15

Slide 15 text

15 Sorbet 01

Slide 16

Slide 16 text

Sorbet ﹘ fast ﹘ expressive ﹘ static ﹘ gradual type checker for Ruby 16

Slide 17

Slide 17 text

17 def foo(opts) opts.fetch("bar") end foo(bar: 1) foo.rb

Slide 18

Slide 18 text

18 # typed: true extend(T::Sig) sig do params(opts: T::Hash[Symbol, Integer]) .returns(Integer) end def foo(opts) opts.fetch("bar") end foo(bar: 1) foo.rb

Slide 19

Slide 19 text

19 foo.rb:9: Expected Symbol but found String("bar") for argument arg0 https://srb.help/7002 10 | opts.fetch("bar") ^^^^^^^^^^^^^^^^^ hash.rbi#L517: Method Hash#fetch has specified arg0 as Symbol 517 | arg0: K, ^^^^ Got String("bar") originating from: foo.rb:9: 10 | opts.fetch("bar") ^^^^^ Errors: 1 srb tc foo.rb

Slide 20

Slide 20 text

20 # typed: true require "view_gem" View.render foo.rb

Slide 21

Slide 21 text

view.rbi 21 # typed: true require "view_gem" View.render foo.rb # typed: true class View def self.render(file) end end

Slide 22

Slide 22 text

view.rbi 22 # typed: true require "view_gem" View.render # ^ error: Not enough arguments provided for method View.render. Expected: 1, got: 0 foo.rb # typed: true class View def self.render(file) end end

Slide 23

Slide 23 text

23 Our Journey 02

Slide 24

Slide 24 text

24 Sorbet is mandatory in CI May 2019 Sorbet over all files Mar 2019 Sorbet is open-sourced Jun 2019 Shopify gets access to Sorbet Jul 2018 Sorbet introduced in Core Oct 2018 Timeline

Slide 25

Slide 25 text

We paid the early adopter price 25

Slide 26

Slide 26 text

26 Built a runtime component No runtime component Problem ﹘ Initial version built in-house (waffle-cone) ﹘ Shared our work with the Sorbet team ﹘ Later on switched to sorbet-runtime

Slide 27

Slide 27 text

27 Built a gem RBI generator All constants must resolve Problem ﹘ Initial version built on YARD ﹘ Decided that structure of constants more important ﹘ Built a runtime reflection based tool ﹘ Open sourced as tapioca

Slide 28

Slide 28 text

28 Built Rubocop rules Some Ruby constructs are not supported Problem ﹘ Initial rules bundled with waffle-cone ﹘ Extracted out to rubocop-sorbet ﹘ Open sourced

Slide 29

Slide 29 text

29 Built DSL plugins for Sorbet Metaprogramming Problem ﹘ Based on simple class method name matching ﹘ Can run Ruby scripts to generate interface ﹘ Contributed to Sorbet, documented ﹘ Slow and error-prone

Slide 30

Slide 30 text

30 RBI generator for models Rails support Problem ﹘ Rake task to generate RBI files for AR models ﹘ Based on sorbet-rails ﹘ Planning switch to sorbet-rails

Slide 31

Slide 31 text

31 Results 03

Slide 32

Slide 32 text

Strictness levels of non-test files 32

Slide 33

Slide 33 text

Percentage of checked calls 33

Slide 34

Slide 34 text

Developer invocations of type check 34

Slide 35

Slide 35 text

Evolution of method signatures 35

Slide 36

Slide 36 text

Testimonials Likes 36 “We get quicker feedback than tests or CI”

Slide 37

Slide 37 text

Testimonials Likes 37 “Allowed us to write less tests”

Slide 38

Slide 38 text

Testimonials Likes 38 “Both static and runtime type checkers caught errors not caught by tests alone”

Slide 39

Slide 39 text

Testimonials Likes 39 “Improves code quality, encourages better code design”

Slide 40

Slide 40 text

Testimonials Likes 40 “Evergreen documentation, makes onboarding easier”

Slide 41

Slide 41 text

Testimonials Dislikes 41 “Syntax is verbose, it is not DRY”

Slide 42

Slide 42 text

Testimonials Dislikes 42 “Hard to add types to existing code”

Slide 43

Slide 43 text

Testimonials Dislikes 43 “Rails and metaprogramming support is not complete yet”

Slide 44

Slide 44 text

44 Pitfalls 04

Slide 45

Slide 45 text

45 Metaprogramming support ﹘ If you use a lot of DSLs, you will need to generate RBI files for them. ﹘ Limited ability to extend Sorbet to understand more syntax. DSL plugins too slow. Pitfall 1

Slide 46

Slide 46 text

46 Some missing stdlib signatures ﹘ Stdlib/core method signatures are maintained by the Sorbet team. ﹘ No full coverage, so you might get some errors for perfectly valid Ruby code. ﹘ Contribute missing signatures back to Sorbet. Pitfall 2

Slide 47

Slide 47 text

47 No constants via inheritance class Foo Bar = 42 end class Baz < Foo end Baz::Bar # error: Unable to resolve constant Bar Foo::Bar # ok Pitfall 3

Slide 48

Slide 48 text

48 No dynamic superclass/mixin def superclass Class.new end def mixin Module.new end class Baz < superclass # error: ^ Superclasses must only contain constant literals include mixin # error: ^ include must only contain constant literals end Pitfall 4

Slide 49

Slide 49 text

49 Runtime type checking overhead ﹘ Checking types at runtime adds ~7% overhead. ﹘ Not a problem for dev and test. ﹘ Might not want it for production. Pitfall 5

Slide 50

Slide 50 text

50 How to get started 05

Slide 51

Slide 51 text

51 Sorbet Playground ﹘ Play around at https://sorbet.run to get familiar ﹘ Read the docs at https://sorbet.org/docs/ Step 1

Slide 52

Slide 52 text

52 Add Sorbet to your project ﹘ Add sorbet, sorbet-runtime to your Gemfile ﹘ Run bundle exec srb init Step 2

Slide 53

Slide 53 text

53 Add Sorbet to your project ﹘ Add sorbet, sorbet-runtime and tapioca to your Gemfile ﹘ Run bundle exec tapioca init ﹘ Run bundle exec tapioca sync Step 2 (alt)

Slide 54

Slide 54 text

54 Start by typing new code ﹘ Easier to type when adding new code. ﹘ Add types to existing code when it is easy and convenient. ﹘ Use rubocop-sorbet to add template signatures to existing code in bulk Step 3

Slide 55

Slide 55 text

55 Lean on gradual typing ﹘ You can type as much or as little as you want. ﹘ Start small, increase coverage slowly. ﹘ Don’t break other people’s workflows. Step 4

Slide 56

Slide 56 text

56 Don’t overtype ﹘ It is OK to use simpler signatures ﹘ There is no harm in using T.untyped when needed. ﹘ Your colleagues should not need a PhD in type theory to make code changes. Step 5

Slide 57

Slide 57 text

57 Track your progress ﹘ Sorbet metrics via --metrics-file flag ﹘ Easy to parse JSON format ﹘ Set up a nightly task and dashboard to track progress Step 6

Slide 58

Slide 58 text

Thank you Don’t forget to fasten your seatbelts! Reach out to me Twitter: @paracycle Github: @paracycle Join us at the Shopify booth!