Pro Yearly is on sale from $80 to $50! »

Adopting Sorbet at Scale

Adopting Sorbet at Scale

Presented at RubyConf 2019

2bb923c57a1fdc3a2eb484bb8d565fd2?s=128

Ufuk Kayserilioglu

November 19, 2019
Tweet

Transcript

  1. 1

  2. 2 Seatbelts

  3. 3 Seatbelts save lives

  4. Benefits 50% Reduction in serious injury 45% Reduction in risk

    of death 4 https://www.cdc.gov/motorvehiclesafety/seatbeltbrief/index.html
  5. 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
  6. 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
  7. “But cars are safe. We test them!” 7

  8. - Costs too much ⏱ and - Cannot test every

    situation (Crash) Testing 8
  9. The higher the stakes, the safer the car 9

  10. 10 Ufuk Kayserilioğlu Rails and Ruby Infrastructure Team

  11. Shopify Scale Commerce 1M Merchants 8000 orders/min (peak) 12B USD

    sales/year 11 135B USD total sales
  12. Shopify Scale Engineering 1500 RnD Employees ~21K Ruby files ~150K

    tests 12 ~40 deploys daily
  13. 13 There is too much at stake here. We need

    seatbelts!
  14. Adopting Sorbet at Scale

  15. 15 Sorbet 01

  16. Sorbet ﹘ fast ﹘ expressive ﹘ static ﹘ gradual type

    checker for Ruby 16
  17. 17 def foo(opts) opts.fetch("bar") end foo(bar: 1) foo.rb

  18. 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
  19. 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
  20. 20 # typed: true require "view_gem" View.render foo.rb

  21. view.rbi 21 # typed: true require "view_gem" View.render foo.rb #

    typed: true class View def self.render(file) end end
  22. 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
  23. 23 Our Journey 02

  24. 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
  25. We paid the early adopter price 25

  26. 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
  27. 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
  28. 28 Built Rubocop rules Some Ruby constructs are not supported

    Problem ﹘ Initial rules bundled with waffle-cone ﹘ Extracted out to rubocop-sorbet ﹘ Open sourced
  29. 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
  30. 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
  31. 31 Results 03

  32. Strictness levels of non-test files 32

  33. Percentage of checked calls 33

  34. Developer invocations of type check 34

  35. Evolution of method signatures 35

  36. Testimonials Likes 36 “We get quicker feedback than tests or

    CI”
  37. Testimonials Likes 37 “Allowed us to write less tests”

  38. Testimonials Likes 38 “Both static and runtime type checkers caught

    errors not caught by tests alone”
  39. Testimonials Likes 39 “Improves code quality, encourages better code design”

  40. Testimonials Likes 40 “Evergreen documentation, makes onboarding easier”

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

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

  43. Testimonials Dislikes 43 “Rails and metaprogramming support is not complete

    yet”
  44. 44 Pitfalls 04

  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 50 How to get started 05

  51. 51 Sorbet Playground ﹘ Play around at https://sorbet.run to get

    familiar ﹘ Read the docs at https://sorbet.org/docs/ Step 1
  52. 52 Add Sorbet to your project ﹘ Add sorbet, sorbet-runtime

    to your Gemfile ﹘ Run bundle exec srb init Step 2
  53. 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)
  54. 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
  55. 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
  56. 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
  57. 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
  58. Thank you Don’t forget to fasten your seatbelts! Reach out

    to me Twitter: @paracycle Github: @paracycle Join us at the Shopify booth!