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

A Brewer's Guide to Filtering out Complexity and Churn (RailsConf 2023 Edition)

A Brewer's Guide to Filtering out Complexity and Churn (RailsConf 2023 Edition)

RailsConf 2023 presentation by Alan Ridlehoover & Fito von Zastrow

Mechanical coffee machines are amazing! You drop in a coin, listen for the clink, make a selection, and the machine springs to life, hissing, clicking, and whirring. Then the complex mechanical ballet ends, splashing that glorious, aromatic liquid into the cup. Ah! C’est magnifique!

There’s just one problem. Our customers also want soup! And, our machine is not extensible. So, we have a choice: we can add to the complexity of our machine by jamming in a new dispenser with each new request; or, we can pause to make our machine more extensible before development slows to a halt.

Here's the video of the presentation: https://youtu.be/jE1Wp7-wnSM

Alan Ridlehoover

April 25, 2023
Tweet

More Decks by Alan Ridlehoover

Other Decks in Programming

Transcript

  1. Alan Ridlehoover & Fito von Zastrow
    RailsConf 2023
    The Coffee
    Machine Talk
    A Brewer’s Guide to Filtering out
    Complexity and Churn

    View full-size slide

  2. PRONOUNS
    he/him/his
    HOMETOWN
    Asunción

    RUBY EXPERIENCE
    12 years
    FAVORITE COFFEE
    Ghirardelli Dark
    Chocolate Mocha
    PRONOUNS
    he/him/his
    HOMETOWN
    Seattle

    RUBY EXPERIENCE
    12 years
    FAVORITE COFFEE
    New Orleans Cold
    Brew w/ Chicory

    View full-size slide

  3. How did we get here?
    How complexity sneaks into a code base.

    View full-size slide

  4. class CoffeeMachine


    def vend


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispose_of_grounds


    end


    end
    ☕ Story: As a patron, I want coffee

    View full-size slide

  5. ☕ Story: As a patron, I want tea
    class CoffeeMachine


    def vend(drink: :coffee)


    if drink == :coffee


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispose_of_grounds


    elsif drink == :tea


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    end


    end


    end

    View full-size slide

  6. ☕ Refactor: DRY up the code
    class CoffeeMachine


    def vend(drink: :coffee)


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    end


    dispense_water


    dispose_of_grounds if drink == :coffee


    end


    end


    View full-size slide

  7. ☕ Story: As a patron, I want sweetener
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    end


    dispense_water


    dispense_sweetener if options[:sweet]


    dispose_of_grounds if drink == :coffee


    end


    end

    View full-size slide

  8. class CoffeeMachine


    def vend(drink: :coffee, options: {})


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    end


    dispense_water


    dispense_sweetener if options[:sweet]


    dispense_cream if options[:creamy]


    dispose_of_grounds if drink == :coffee


    end


    end


    ☕ Story: As a patron, I want cream

    View full-size slide

  9. ☕ Story: As a patron, I want cocoa
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    elsif drink == :cocoa


    dispense_cocoa_mix


    end


    dispense_water


    unless drink == :cocoa


    dispense_sweetener if options[:sweet]


    dispense_creamer if options[:creamy]


    end


    dispose_of_grounds if drink == :coffee


    end


    end

    View full-size slide

  10. ☕ Story: As a patron, I want whipped cream
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    elsif drink == :cocoa


    dispense_cocoa_mix


    end


    dispense_water


    unless drink == :cocoa


    dispense_sweetener if options[:sweet]


    dispense_creamer if options[:creamy]


    end


    unless drink == :tea


    dispense_whipped_cream if options[:fluffy]


    end


    dispose_of_grounds if drink == :coffee


    end


    end

    View full-size slide

  11. class CoffeeMachine


    def vend(drink: :coffee, options: {})


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    elsif drink == :cocoa


    dispense_cocoa_mix


    end


    dispense_water


    unless drink == :cocoa


    dispense_sweetener if options[:sweet]


    dispense_creamer if options[:creamy]


    end


    unless drink == :tea


    dispense_whipped_cream if options[:fluffy]


    end


    dispose_of_grounds if drink == :coffee


    end


    end
    ☕ 7 commits: 9 conditionals
    1
    2
    3
    4 5
    6
    7 8
    9

    View full-size slide

  12. Do you smell that?
    How to recognize the problem before it
    becomes painful and frustrating.
    Rules by Sandi Metz Flog by Ryan Davis

    View full-size slide

  13. Score of Means


    - - - - - - - - - - - - - - - - - - - - - - - - -


    0-10 Awesome


    11-20 Good enough


    21-40 Might need refactoring


    41-60 Possible to justify


    61-100 Danger


    100-200 Whoop, whoop, whoop


    200 + Someone please think of the children


    (note: these are scores for an individual method)


    What's a good
    fl
    og score? by Jake Scruggs
    ☕ Measuring complexity with the flog gem

    View full-size slide

  14. ☕ Story: As a patron, I want coffee
    class CoffeeMachine


    def vend


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispose_of_grounds


    end


    end
    Churn

    1
    Complexity

    5.5
    10
    20
    30
    25
    15
    5
    0

    View full-size slide

  15. class CoffeeMachine


    def vend(drink: :coffee)


    if drink == :coffee


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispose_of_grounds


    elsif drink == :tea


    dispense_cup


    dispense_tea_bag


    heat_water


    dispense_water


    end


    end


    end
    Churn

    2
    Complexity

    14.6
    10
    20
    30
    25
    15
    5
    0
    ☕ Story: As a patron, I want tea

    View full-size slide

  16. ☕ Refactor: DRY up the code
    class CoffeeMachine


    def vend(drink: :coffee)


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    end


    dispense_water


    dispose_of_grounds if drink == :coffee


    end


    end


    Churn

    3
    Complexity

    10.9
    10
    20
    30
    25
    15
    5
    0

    View full-size slide

  17. ☕ Story: As a patron, I want sweetener
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    end


    dispense_water


    dispense_sweetener if options[:sweet]


    dispose_of_grounds if drink == :coffee


    end


    end


    Churn

    4
    Complexity

    13.5
    10
    20
    30
    25
    15
    5
    0

    View full-size slide

  18. ☕ Story: As a patron, I want cream
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    end


    dispense_water


    dispense_sweetener if options[:sweet]


    dispense_cream if options[:creamy]


    dispose_of_grounds if drink == :coffee


    end


    end


    Churn

    5
    Complexity

    16.0
    10
    20
    30
    25
    15
    5
    0

    View full-size slide

  19. ☕ Story: As a patron, I want cocoa
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    elsif drink == :cocoa


    dispense_cocoa_mix


    end


    dispense_water


    unless drink == :cocoa


    dispense_sweetener if options[:sweet]


    dispense_cream if options[:creamy]


    end


    dispose_of_grounds if drink == :coffee


    end


    end


    Churn

    6
    Complexity

    21.3
    10
    20
    30
    25
    15
    5
    0

    View full-size slide

  20. ☕ Story: As a patron, I want whipped cream
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    elsif drink == :cocoa


    dispense_cocoa_mix


    end


    dispense_water


    unless drink == :cocoa


    dispense_sweetener if options[:sweet]


    dispense_cream if options[:creamy]


    end


    unless drink == :tea


    dispense_whipped_cream if options[:fluffy]


    end


    dispose_of_grounds if drink == :coffee


    end


    end


    Churn

    7
    Complexity

    25.5
    10
    20
    30
    25
    15
    5
    0

    View full-size slide

  21. ☕ CoffeeMachine pain index over time
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    elsif drink == :cocoa


    dispense_cocoa_mix


    end


    dispense_water


    unless drink == :cocoa


    dispense_sweetener if options[:sweet]


    dispense_cream if options[:creamy]


    end


    unless drink == :tea


    dispense_whipped_cream if options[:fluffy]


    end


    dispose_of_grounds if drink == :coffee


    end


    end


    Churn

    7
    Complexity

    25.5
    10
    20
    30
    25
    15
    5
    0

    View full-size slide

  22. ☕ CoffeeMachine pain index over time
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    elsif drink == :cocoa


    dispense_cocoa_mix


    end


    dispense_water


    unless drink == :cocoa


    dispense_sweetener if options[:sweet]


    dispense_cream if options[:creamy]


    end


    unless drink == :tea


    dispense_whipped_cream if options[:fluffy]


    end


    dispose_of_grounds if drink == :coffee


    end


    end


    Churn

    7
    Complexity

    25.5
    10
    20
    30
    25
    15
    5
    0
    Complexity

    25.5

    View full-size slide

  23. Can we turn it around?
    How to
    fi
    lter out complexity and churn.

    View full-size slide

  24. ☕ Refactor: Rehydrate to see missing abstractions
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    elsif drink == :cocoa


    dispense_cocoa_mix


    end


    dispense_water


    unless drink == :cocoa


    dispense_sweetener if options[:sweet]


    dispense_cream if options[:creamy]


    end


    unless drink == :tea


    dispense_whipped_cream if options[:fluffy]


    end


    dispose_of_grounds if drink == :coffee


    end


    end


    View full-size slide

  25. ☕ Refactor: Rehydrate to see missing abstractions

    View full-size slide

  26. ☕ Refactor: Rehydrate to see missing abstractions
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    dispense_cup


    heat_water


    if drink == :coffee


    prepare_grounds


    elsif drink == :tea


    dispense_tea_bag


    elsif drink == :cocoa


    dispense_cocoa_mix


    end


    dispense_water


    unless drink == :cocoa


    dispense_sweetener if options[:sweet]


    dispense_cream if options[:creamy]


    end


    unless drink == :tea


    dispense_whipped_cream if options[:fluffy]


    end


    dispose_of_grounds if drink == :coffee


    end


    end


    View full-size slide

  27. ☕ Refactor: Rehydrate to see missing abstractions
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    if drink == :coffee


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if options[:sweet]


    dispense_cream if options[:creamy]


    dispense_whipped_cream if options[:fluffy]


    dispose_of_grounds


    elsif drink == :tea


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if options[:sweet]


    dispense_cream if options[:creamy]


    elsif drink == :cocoa


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if options[:fluffy]


    end


    end


    end


    View full-size slide

  28. ☕ Refactor: Extract polymorphic objects
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    if drink == :coffee


    Coffee.new(options: options).prepare


    elsif drink == :tea


    Tea.new(options: options).prepare


    elsif drink == :cocoa


    Cocoa.new(options: options).prepare


    end


    end


    end


    class Coffee < Beverage


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end



    class Cocoa < Beverage


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    View full-size slide

  29. ☕ Refactor: Extract polymorphic objects
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    if drink == :coffee


    Coffee.new(options: options).prepare


    elsif drink == :tea


    Tea.new(options: options).prepare


    elsif drink == :cocoa


    Cocoa.new(options: options).prepare


    end


    end


    end


    class Coffee < Beverage


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end



    class Cocoa < Beverage


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    View full-size slide

  30. ☕ Refactor: Extract polymorphic objects
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(beverage, options)


    if beverage == :coffee


    Coffee.new(options)


    elsif beverage == :tea


    Tea.new(options)


    elsif beverage == :cocoa


    Cocoa.new(options)


    end


    end


    end


    class Coffee < Beverage


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end



    class Cocoa < Beverage


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    View full-size slide

  31. ☕ Refactor: Introduce the NullObject pattern
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(beverage, options)


    if beverage == :coffee


    Coffee.new(options)


    elsif beverage == :tea


    Tea.new(options)


    elsif beverage == :cocoa


    Cocoa.new(options)


    else


    NullBeverage.new(options)


    end


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end



    class Cocoa < Beverage


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    View full-size slide

  32. ☕ Refactor: Use convention to prevent churn
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(beverage, options)


    klass = beverage.capitalize


    if Module.const_defined?(klass)


    Module.const_get(klass).new(options)


    else


    NullBeverage.new(options)


    end


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end



    class Cocoa < Beverage


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    View full-size slide

  33. Self Registration
    How to create an open/closed factory class.
    🚧

    View full-size slide

  34. ☕ Refactor: Introduce self-registration
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(beverage, options)


    klass = beverage.capitalize


    if Module.const_defined?(klass)


    Module.const_get(klass).new(options)


    else


    NullBeverage.new(options)


    end


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end
    class Cocoa < Beverage


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    View full-size slide

  35. ☕ Refactor: Introduce self-registration
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    BeverageFactory.register(:coffee, self)


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    BeverageFactory.register(:tea, self)


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end
    class Cocoa < Beverage


    BeverageFactory.register(:cocoa, self)


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    View full-size slide

  36. ☕ Refactor: Introduce self-registration
    class Coffee < Beverage


    BeverageFactory.register(:coffee, self)


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    BeverageFactory.register(:tea, self)


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end
    class Cocoa < Beverage


    BeverageFactory.register(:cocoa, self)


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    View full-size slide

  37. ☕ Refactor: Introduce self-registration
    class Coffee < Beverage


    BeverageFactory.register(:coffee, self)


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    BeverageFactory.register(:tea, self)


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end
    class Cocoa < Beverage


    BeverageFactory.register(:cocoa, self)


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    View full-size slide

  38. ☕ Refactor: Introduce self-registration
    class Coffee < Beverage


    BeverageFactory.register(:coffee, self)


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    BeverageFactory.register(:tea, self)


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end
    class Cocoa < Beverage


    BeverageFactory.register(:cocoa, self)


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    View full-size slide

  39. class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    BeverageFactory.register(:coffee, self)

    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    BeverageFactory.register(:tea, self)


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end
    class Cocoa < Beverage


    BeverageFactory.register(:cocoa, self)


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    ☕ Refactor: Introduce self-registration

    View full-size slide

  40. class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    # {


    # :coffee => Coffee,

    # :tea => Tea,


    # :cocoa => Cocoa


    # )


    class Coffee < Beverage


    BeverageFactory.register(:coffee, self)


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    BeverageFactory.register(:tea, self)


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end
    class Cocoa < Beverage


    BeverageFactory.register(:cocoa, self)


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    ☕ Refactor: Introduce self-registration

    View full-size slide

  41. class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    BeverageFactory.register(:coffee, self)


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    BeverageFactory.register(:tea, self)


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end
    class Cocoa < Beverage


    BeverageFactory.register(:cocoa, self)


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    ☕ Refactor: Introduce self-registration

    View full-size slide

  42. class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    BeverageFactory.register(:coffee, self)


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    BeverageFactory.register(:tea, self)


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end
    class Cocoa < Beverage


    BeverageFactory.register(:cocoa, self)


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    ☕ Refactor: Introduce self-registration

    View full-size slide

  43. class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    prepares :coffee


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    prepares :tea


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end


    class Beverage


    def self.prepares(key)


    BeverageFactory.register(key, self)


    end


    # initializer & other shared code


    end


    class Cocoa < Beverage


    prepares :cocoa


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    ☕ Refactor: Introduce self-registration

    View full-size slide

  44. ☕ Refactor: Introduce self-registration
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    prepares :coffee


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    prepares :tea


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end


    class Beverage


    def self.prepares(key)


    BeverageFactory.register(key, self)


    end


    # initializer & other shared code


    end


    class Cocoa < Beverage


    prepares :cocoa


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    View full-size slide

  45. https://github.com/
    fi
    rst-try-software/manufacturable
    ☕ Manufacturable: A gem for self-registration

    View full-size slide

  46. Did that work?
    Were we able to reduce the complexity?

    View full-size slide

  47. ☕ Complexity of the CoffeeMachine class over time
    Rehydrate
    Extract Classes
    Introduce Factory
    Whipped Cream
    Cocoa
    Cream
    Sweetener
    DRY
    Tea
    Co
    ff
    ee
    10
    20
    30
    25
    15
    5
    0
    40
    35

    View full-size slide

  48. ☕ Complexity of the CoffeeMachine class over time
    Rehydrate
    Extract Classes
    Introduce Factory
    Whipped Cream
    Cocoa
    Cream
    Sweetener
    DRY
    Tea
    Co
    ff
    ee
    10
    20
    30
    25
    15
    5
    0
    40
    35

    View full-size slide

  49. ☕ Complexity of the CoffeeMachine class over time
    Rehydrate
    Extract Classes
    Introduce Factory
    Whipped Cream
    Cocoa
    Cream
    Sweetener
    DRY
    Tea
    Co
    ff
    ee
    10
    20
    30
    25
    15
    5
    0
    40
    35

    View full-size slide

  50. ☕ Complexity of the CoffeeMachine class over time
    Rehydrate
    Extract Classes
    Introduce Factory

    and self-registration
    Whipped Cream
    Cocoa
    Cream
    Sweetener
    DRY
    Tea
    Co
    ff
    ee
    10
    20
    30
    25
    15
    5
    0
    40
    35

    View full-size slide

  51. ☕ Complexity of the application
    Class Churn Complexity


    --------------------- ----- ----------


    CoffeeMachine 10 3.9


    BeverageFactory 3 1.8


    Beverage 2 2.2

    Coffee 1 16.6


    Tea 1 11.8


    Cocoa 1 8.1


    NullBeverage 1 0.0

    View full-size slide

  52. Now, who wants ?
    How to add functionality without modifying existing
    fi
    les.

    View full-size slide

  53. ☕ Refactor: Introduce self-registration
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    prepares :coffee


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    prepares :tea


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end


    class Beverage


    def self.prepares(key)


    BeverageFactory.register(key, self)


    end


    # initializer & other shared code


    end


    class Cocoa < Beverage


    prepares :cocoa


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end

    View full-size slide

  54. ☕ Refactor: Introduce self-registration
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    prepares :coffee


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    prepares :tea


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end


    class Beverage


    def self.prepares(key)


    BeverageFactory.register(key, self)


    end


    # initializer & other shared code


    end


    class Cocoa < Beverage


    prepares :cocoa


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    class TomatoSoup < Beverage


    prepares :tomato_soup


    def prepare


    dispense_cup


    heat_water


    dispense_condensed_soup


    dispense_water


    dispense_croutons if crunchy?


    dispense_hot_sauce if spicy?


    end


    end

    View full-size slide

  55. ☕ Refactor: Introduce self-registration
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    prepares :coffee


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    prepares :tea


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end


    class Beverage


    def self.prepares(key)


    BeverageFactory.register(key, self)


    end


    # initializer & other shared code


    end


    class Cocoa < Beverage


    prepares :cocoa


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end

    View full-size slide

  56. ☕ Refactor: Introduce self-registration
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    prepares :coffee


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    prepares :tea


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end


    class Beverage


    def self.prepares(key)


    BeverageFactory.register(key, self)


    end


    # initializer & other shared code


    end


    class Cocoa < Beverage


    prepares :cocoa


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    class TomatoSoup < Beverage


    prepares :tomato_soup


    def prepare


    dispense_cup


    heat_water


    dispense_condensed_soup


    dispense_water


    dispense_croutons if crunchy?


    dispense_hot_sauce if spicy?


    end


    end

    View full-size slide

  57. ☕ Refactor: Introduce self-registration
    class CoffeeMachine


    def vend(drink: :coffee, options: {})


    BeverageFactory.build(drink, options).prepare


    end


    end


    class BeverageFactory


    def self.build(key, options)


    beverage_class(key).new(options)


    end


    def self.register(key, klass)


    beverages[key] = klass


    end


    def self.beverage_class(key)


    beverages[key] || NullBeverage


    end


    def self.beverages


    @beverages ||= {}


    end


    end


    class NullBeverage < Beverage


    def prepare


    # no-op


    end


    end


    class Coffee < Beverage


    prepares :coffee


    def prepare


    dispense_cup


    heat_water


    prepare_grounds


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    dispense_whipped_cream if fluffy?


    dispose_of_grounds


    end


    end



    class Tea < Beverage


    prepares :tea


    def prepare


    dispense_cup


    heat_water


    dispense_tea_bag


    dispense_water


    dispense_sweetener if sweet?


    dispense_cream if creamy?


    end


    end


    class Beverage


    def self.prepares(key)


    BeverageFactory.register(key, self)


    end


    # initializer & other shared code


    end


    class Cocoa < Beverage


    prepares :cocoa


    def prepare


    dispense_cup


    heat_water


    dispense_cocoa_mix


    dispense_water


    dispense_whipped_cream if fluffy?


    end


    end


    class TomatoSoup < Beverage


    prepares :tomato_soup


    def prepare


    dispense_cup


    heat_water


    dispense_condensed_soup


    dispense_water


    dispense_croutons if crunchy?


    dispense_hot_sauce if spicy?


    end


    end


    class AppleCider < Beverage


    prepares :apple_cider


    def prepare


    dispense_cup


    heat_water


    dispense_cider_mix


    dispense_water


    dispense_cinnamon_stick


    end


    end


    View full-size slide

  58. Yeah, but does it scale?
    How to apply this to a large, complicated code base.

    View full-size slide

  59. ☕ Plotting complexity vs. churn
    Churn
    Complexity
    Getting Empirical about Refactoring by Michael Feathers

    View full-size slide

  60. ☕ Plotting complexity vs. churn
    Churn
    Complexity
    Pain Free

    View full-size slide

  61. ☕ Plotting complexity vs. churn
    Pain Free
    Painful
    Churn
    Complexity

    View full-size slide

  62. ☕ Plotting complexity vs. churn
    Pain Free
    Painful
    Churn
    Complexity
    Configuration?

    View full-size slide

  63. ☕ Plotting complexity vs. churn
    Pain Free
    Painful
    Churn
    Complexity
    Configuration?
    Omega Mess?
    Go Ahead, Make a Mess by Sandi Metz

    View full-size slide

  64. ☕ Plotting complexity vs. churn
    Churn
    Complexity

    View full-size slide

  65. ☕ Plotting complexity vs. churn
    Churn
    Complexity

    View full-size slide

  66. ☕ Plotting complexity vs. churn
    Churn
    Complexity

    View full-size slide

  67. Conclusion
    A Brewer’s Guide to Filtering out Complexity and Churn

    View full-size slide

  68. Take Aways
    A Brewer’s Guide to Filtering out Complexity and Churn

    View full-size slide

  69. Take Aways
    A Brewer’s Guide to Filtering out Complexity and Churn
    Complexity will sneak into your code.
    • Happens one commit at a time
    • Conditionals and DRY are often the culprits

    View full-size slide

  70. Take Aways
    A Brewer’s Guide to Filtering out Complexity and Churn
    Complexity will sneak into your code.
    • Happens one commit at a time
    • Conditionals and DRY are often the culprits
    You can recognize complexity before it becomes painful.
    • Keep methods short
    • Watch complexity
    • Pay attention to how it feels

    View full-size slide

  71. Take Aways
    A Brewer’s Guide to Filtering out Complexity and Churn
    Complexity will sneak into your code.
    • Happens one commit at a time
    • Conditionals and DRY are often the culprits
    You can recognize complexity before it becomes painful.
    • Keep methods short
    • Watch complexity
    • Pay attention to how it feels
    And, you can back away from painful complexity.
    • Polymorphism and factories are your friends
    • Overly DRY code can hide abstractions and couple algorithms in dangerous ways
    • Self-registration can take you across the no-churn
    fi
    nish line

    View full-size slide

  72. Homework
    A Brewer’s Guide to Filtering out Complexity and Churn

    View full-size slide

  73. Homework
    What is the average method complexity for your application?
    • Use the Flog gem to measure complexity
    A Brewer’s Guide to Filtering out Complexity and Churn

    View full-size slide

  74. Homework
    What is the
    fi
    le with the most churn in your application?
    • Use the Churn gem to measure churn
    What is the average method complexity for your application?
    • Use the Flog gem to measure complexity
    A Brewer’s Guide to Filtering out Complexity and Churn

    View full-size slide

  75. Homework
    What is the
    fi
    le with the most churn in your application?
    • Use the Churn gem to measure churn
    Which class in your application needs the most attention?
    • Use the Ruby Critic gem to plot complexity vs. churn
    • Look for the
    fi
    le with the highest complexity and churn
    What is the average method complexity for your application?
    • Use the Flog gem to measure complexity
    A Brewer’s Guide to Filtering out Complexity and Churn

    View full-size slide

  76. Homework
    What is the
    fi
    le with the most churn in your application?
    • Use the Churn gem to measure churn
    Which class in your application needs the most attention?
    • Use the Ruby Critic gem to plot complexity vs. churn
    • Look for the
    fi
    le with the highest complexity and churn
    What is the average method complexity for your application?
    • Use the Flog gem to measure complexity
    A Brewer’s Guide to Filtering out Complexity and Churn
    Where might you use self-registration in your code?
    • Play with the technique
    fi
    rst to get a feel for it
    • Look at the Manufacturable gem for industrial-scale self-registration

    View full-size slide

  77. Get in touch!
    EMAIL
    fi
    [email protected]
    GITHUB
    @Fito
    TWITTER
    @
    fi
    to.vonzastrow
    MASTODON
    @[email protected]
    EMAIL
    [email protected]
    GITHUB
    @aridlehoover

    TWITTER
    @aridlehoover
    MASTODON
    @[email protected]
    BLOG
    the.codegardener.com
    GITHUB REPO
    https://github.com/
    fi
    rst-try-software/co
    ff
    ee

    View full-size slide

  78. Ruby Flog
    Ruby Flog
    Ruby Flog


    A VS Code extension that puts
    fl
    og scores in your status bar…

    View full-size slide

  79. We are the largest Rails shop you’ve never heard of.
    We build Internet machines for co
    ff
    ee lovers.
    Our 16 year old, 1.5 million line monolith supports:
    • A multi-billion dollar business
    • Billions of requests per day
    Our team is solving hard problems related to:
    • Code complexity
    • Rails at scale
    https://meraki.cisco.com/jobs

    View full-size slide

  80. Thanks for coming!
    A Brewer’s Guide to Filtering out Complexity and Churn
    Sandi Metz
    • Practical Object Oriented Design in Ruby (book)

    • 99 Bottles of OOP (book) (sample on the topic of measuring complexity)

    • Rules: https://youtu.be/npOGOmkxuio

    • Go Ahead, Make a Mess: https://youtu.be/mpA2F1In41w

    Ryan Davis
    • Flog: https://github.com/seattlerb/
    fl
    og
    Jake Scruggs
    • https://jakescruggs.blogspot.com/2008/08/whats-good-
    fl
    og-score.html

    Michael Feathers
    • Working E
    ff
    ectively with Legacy Code (book)

    • https://www.agileconnection.com/article/getting-empirical-about-refactoring

    Alan Ridlehoover & Fito von Zastrow
    • Manufacturable: https://github.com/
    fi
    rst-try-software/manufacturable

    • Ruby Flog: https://marketplace.visualstudio.com/items?itemName=
    fi
    rst-try-software.ruby-
    fl
    og

    View full-size slide