Slide 1

Slide 1 text

Alan Ridlehoover & Fito von Zastrow RubyConf Mini 2022 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

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

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 dispense_tea_bag heat_water 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

☕ 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

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

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

Slide 17

Slide 17 text

☕ Measuring complexity with the flog gem 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

Slide 18

Slide 18 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 19

Slide 19 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 dispense_tea_bag heat_water dispense_water end end end Churn 2 Complexity 14.6 10 20 30 25 15 5 0

Slide 20

Slide 20 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 21

Slide 21 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 22

Slide 22 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 23

Slide 23 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 Churn 6 Complexity 21.3 10 20 30 25 15 5 0

Slide 24

Slide 24 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 Churn 7 Complexity 25.5 10 20 30 25 15 5 0

Slide 25

Slide 25 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_creamer 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_creamer 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

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

Slide 28

Slide 28 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_creamer if options[:creamy] end unless drink == :tea dispense_whipped_cream if options[:fluffy] end dispose_of_grounds if drink == :coffee end end

Slide 29

Slide 29 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_creamer 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_creamer 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 30

Slide 30 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 def prepare dispense_cup heat_water prepare_grounds dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] dispense_whipped_cream if options[:fluffy] dispose_of_grounds end end class Tea def prepare dispense_cup heat_water dispense_tea_bag dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] end end class Cocoa def prepare dispense_cup heat_water dispense_cocoa_mix dispense_water dispense_whipped_cream if options[:fluffy] end end

Slide 31

Slide 31 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 def prepare dispense_cup heat_water prepare_grounds dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] dispense_whipped_cream if options[:fluffy] dispose_of_grounds end end class Tea def prepare dispense_cup heat_water dispense_tea_bag dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] end end class Cocoa def prepare dispense_cup heat_water dispense_cocoa_mix dispense_water dispense_whipped_cream if options[:fluffy] end end

Slide 32

Slide 32 text

☕ Refactor: Extract factory class class CoffeeMachine def vend(drink: :coffee, options: {}) BeverageFactory.build(drink, options).prepare end end class BeverageFactory def self.build(drink, options) if drink == :coffee Coffee.new(options: options) elsif drink == :tea Tea.new(options: options) elsif drink == :cocoa Cocoa.new(options: options) end end end class Coffee def prepare dispense_cup heat_water prepare_grounds dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] dispense_whipped_cream if options[:fluffy] dispose_of_grounds end end class Tea def prepare dispense_cup heat_water dispense_tea_bag dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] end end class Cocoa def prepare dispense_cup heat_water dispense_cocoa_mix dispense_water dispense_whipped_cream if options[:fluffy] end end

Slide 33

Slide 33 text

☕ Refactor: Extract factory class class CoffeeMachine def vend(drink: :coffee, options: {}) BeverageFactory.build(drink, options).prepare end end class BeverageFactory def self.build(drink, options) if drink == :coffee Coffee.new(options: options) elsif drink == :tea Tea.new(options: options) elsif drink == :cocoa Cocoa.new(options: options) else NullBeverage.new(options: options) end end end class NullBeverage def prepare # no-op end end class Coffee def prepare dispense_cup heat_water prepare_grounds dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] dispense_whipped_cream if options[:fluffy] dispose_of_grounds end end class Tea def prepare dispense_cup heat_water dispense_tea_bag dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] end end class Cocoa def prepare dispense_cup heat_water dispense_cocoa_mix dispense_water dispense_whipped_cream if options[:fluffy] end end

Slide 34

Slide 34 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(drink, options) klass = drink.capitalize if Module.const_defined?(klass) Module.const_get(klass).new(options: options) else NullBeverage.new(options: options) end end end class NullBeverage def prepare # no-op end end class Coffee def prepare dispense_cup heat_water prepare_grounds dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] dispense_whipped_cream if options[:fluffy] dispose_of_grounds end end class Tea def prepare dispense_cup heat_water dispense_tea_bag dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] end end class Cocoa def prepare dispense_cup heat_water dispense_cocoa_mix dispense_water dispense_whipped_cream if options[:fluffy] end end

Slide 35

Slide 35 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 36

Slide 36 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 37

Slide 37 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 38

Slide 38 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 39

Slide 39 text

☕ Complexity of the application Class Churn Complexity --------------------- ----- ---------- CoffeeMachine#vend 10 3.9 BeverageFactory#build 2 6.2 Coffee#prepare 1 16.6 Tea#prepare 1 11.8 Cocoa#prepare 1 8.1 NullBeverage#prepare 1 0.0

Slide 40

Slide 40 text

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

Slide 41

Slide 41 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(drink, options) klass = drink.capitalize if Module.const_defined?(klass) Module.const_get(klass).new(options: options) else NullBeverage.new(options: options) end end end class NullBeverage def prepare # no-op end end class Coffee def prepare dispense_cup heat_water prepare_grounds dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] dispense_whipped_cream if options[:fluffy] dispose_of_grounds end end class Tea def prepare dispense_cup heat_water dispense_tea_bag dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] end end class Cocoa def prepare dispense_cup heat_water dispense_cocoa_mix dispense_water dispense_whipped_cream if options[:fluffy] end end

Slide 42

Slide 42 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(drink, options) klass = drink.capitalize if Module.const_defined?(klass) Module.const_get(klass).new(options: options) else NullBeverage.new(options: options) end end end class NullBeverage def prepare # no-op end end class Soup def prepare dispense_cup heat_water dispense_condensed_soup dispense_water dispense_croutons if options[:crunchy] dispense_hot_sauce if options[:spicy] end end class Coffee def prepare dispense_cup heat_water prepare_grounds dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] dispense_whipped_cream if options[:fluffy] dispose_of_grounds end end class Tea def prepare dispense_cup heat_water dispense_tea_bag dispense_water dispense_sweetener if options[:sweet] dispense_creamer if options[:creamy] end end class Cocoa def prepare dispense_cup heat_water dispense_cocoa_mix dispense_water dispense_whipped_cream if options[:fluffy] end end

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

☕ Plotting complexity vs. churn Pain Free Churn Complexity

Slide 46

Slide 46 text

☕ Plotting complexity vs. churn Pain Free Painful Churn Complexity

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

☕ Plotting complexity vs. churn Churn Complexity

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 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 53

Slide 53 text

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

Slide 54

Slide 54 text

Take Aways A Brewer’s Guide to Filtering out Complexity and Churn 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 Complexity will sneak into your code. • Happens one commit at a time • Conditionals and DRY are often the culprits

Slide 55

Slide 55 text

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

Slide 56

Slide 56 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 57

Slide 57 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 58

Slide 58 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 59

Slide 59 text

EMAIL fi [email protected]
 GITHUB @Fito TWITTER @ fi to.vonzastrow EMAIL [email protected] GITHUB @aridlehoover
 TWITTER @aridlehoover BLOG the.codegardener.com GITHUB REPO https://github.com/ fi rst-try-software/co ff ee Get in touch! SLACK CHANNEL #a-brewers-guide-to- fi ltering-out-complexity-and-churn

Slide 60

Slide 60 text

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

Slide 61

Slide 61 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 • Ruby Flog: https://marketplace.visualstudio.com/items?itemName= fi rst-try-software.ruby- fl og