Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Adopting Sorbet at Scale

Adopting Sorbet at Scale

Presented at RubyConf 2019

Ufuk Kayserilioglu

November 19, 2019
Tweet

More Decks by Ufuk Kayserilioglu

Other Decks in Programming

Transcript

  1. 1

    View Slide

  2. 2
    Seatbelts

    View Slide

  3. 3
    Seatbelts save lives

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  9. The higher the stakes, the safer the car
    9

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  14. Adopting Sorbet at Scale

    View Slide

  15. 15
    Sorbet
    01

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  23. 23
    Our Journey
    02

    View Slide

  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

    View Slide

  25. We paid the
    early adopter price
    25

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  31. 31
    Results
    03

    View Slide

  32. Strictness
    levels of
    non-test files
    32

    View Slide

  33. Percentage
    of checked
    calls
    33

    View Slide

  34. Developer
    invocations
    of type check
    34

    View Slide

  35. Evolution of
    method
    signatures
    35

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  44. 44
    Pitfalls
    04

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  50. 50
    How to get started
    05

    View Slide

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

    View Slide

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

    View Slide

  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)

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide