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. 3
    Seatbelts save lives

    View full-size slide

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

    View full-size slide

  3. 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 full-size slide

  4. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  12. Adopting Sorbet at Scale

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  15. 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 full-size slide

  16. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  19. 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 full-size slide

  20. 23
    Our Journey
    02

    View full-size slide

  21. 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 full-size slide

  22. We paid the
    early adopter price
    25

    View full-size slide

  23. 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 full-size slide

  24. 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 full-size slide

  25. 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 full-size slide

  26. 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 full-size slide

  27. 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 full-size slide

  28. Strictness
    levels of
    non-test files
    32

    View full-size slide

  29. Percentage
    of checked
    calls
    33

    View full-size slide

  30. Developer
    invocations
    of type check
    34

    View full-size slide

  31. Evolution of
    method
    signatures
    35

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  40. 44
    Pitfalls
    04

    View full-size slide

  41. 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 full-size slide

  42. 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 full-size slide

  43. 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 full-size slide

  44. 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 full-size slide

  45. 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 full-size slide

  46. 50
    How to get started
    05

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  49. 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 full-size slide

  50. 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 full-size slide

  51. 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 full-size slide

  52. 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 full-size slide

  53. 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 full-size slide

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

    View full-size slide