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
  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
  3. ☕ 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
  4. ☕ 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
  5. ☕ 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
  6. 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
  7. ☕ 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
  8. ☕ 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
  9. 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
  10. Do you smell that? How to recognize the problem before

    it becomes painful and frustrating. Rules by Sandi Metz Flog by Ryan Davis
  11. 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
  12. ☕ 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
  13. 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
  14. ☕ 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
  15. ☕ 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
  16. ☕ 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
  17. ☕ 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
  18. ☕ 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
  19. ☕ 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
  20. ☕ 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
  21. ☕ 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
  22. ☕ 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
  23. ☕ 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
  24. ☕ 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
  25. ☕ 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
  26. ☕ 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
  27. ☕ 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
  28. ☕ 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
  29. ☕ 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
  30. ☕ 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
  31. ☕ 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
  32. ☕ 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
  33. ☕ 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. ☕ 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
  40. ☕ 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
  41. ☕ 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
  42. ☕ 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
  43. ☕ 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
  44. ☕ 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
  45. ☕ 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
  46. ☕ 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
  47. ☕ 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
  48. ☕ 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
  49. ☕ 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
  50. Yeah, but does it scale? How to apply this to

    a large, complicated code base.
  51. ☕ Plotting complexity vs. churn Pain Free Painful Churn Complexity

    Configuration? Omega Mess? Go Ahead, Make a Mess by Sandi Metz
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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
  59. 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
  60. Ruby Flog Ruby Flog Ruby Flog
 
 A VS Code

    extension that puts fl og scores in your status bar…
  61. 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
  62. 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