Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

🤲

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

👍

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

class CoffeeMachine def vend dispense_cup heat_water prepare_grounds dispense_water dispose_of_grounds end end ☕ Story: As a patron, I want coffee

Slide 10

Slide 10 text

☕ 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

Slide 11

Slide 11 text

☕ 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

Slide 12

Slide 12 text

☕ 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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

☕ 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

Slide 15

Slide 15 text

☕ 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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

☕ 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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

☕ 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

Slide 22

Slide 22 text

☕ 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

Slide 23

Slide 23 text

☕ 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

Slide 24

Slide 24 text

☕ 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

Slide 25

Slide 25 text

☕ 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

Slide 26

Slide 26 text

☕ 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

Slide 27

Slide 27 text

☕ 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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

☕ 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

Slide 30

Slide 30 text

☕ Refactor: Rehydrate to see missing abstractions

Slide 31

Slide 31 text

☕ 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

Slide 32

Slide 32 text

☕ 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

Slide 33

Slide 33 text

☕ 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

Slide 34

Slide 34 text

☕ 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

Slide 35

Slide 35 text

☕ 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

Slide 36

Slide 36 text

☕ 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

Slide 37

Slide 37 text

☕ 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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

☕ 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

Slide 40

Slide 40 text

☕ 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

Slide 41

Slide 41 text

☕ 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

Slide 42

Slide 42 text

☕ 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

Slide 43

Slide 43 text

☕ 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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

☕ 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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

☕ 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

Slide 53

Slide 53 text

☕ 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

Slide 54

Slide 54 text

☕ 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

Slide 55

Slide 55 text

☕ 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

Slide 56

Slide 56 text

☕ 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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

☕ 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

Slide 59

Slide 59 text

☕ 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

Slide 60

Slide 60 text

☕ 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

Slide 61

Slide 61 text

☕ 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

Slide 62

Slide 62 text

☕ 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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

☕ Plotting complexity vs. churn Churn Complexity Pain Free

Slide 66

Slide 66 text

☕ Plotting complexity vs. churn Pain Free Painful Churn Complexity

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

☕ Plotting complexity vs. churn Churn Complexity

Slide 70

Slide 70 text

☕ Plotting complexity vs. churn Churn Complexity

Slide 71

Slide 71 text

☕ Plotting complexity vs. churn Churn Complexity

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

Get in touch! EMAIL fi to@meraki.net
 GITHUB @Fito TWITTER @ fi to.vonzastrow MASTODON @Fito@ruby.social EMAIL alan@meraki.net GITHUB @aridlehoover
 TWITTER @aridlehoover MASTODON @alan@ruby.social BLOG the.codegardener.com GITHUB REPO https://github.com/ fi rst-try-software/co ff ee

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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