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

A Brewer's Guide to Filtering out Complexity and Churn (RubyConf Mini 2022 Edition)

A Brewer's Guide to Filtering out Complexity and Churn (RubyConf Mini 2022 Edition)

RubyConf Mini 2022 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 talk: https://youtu.be/RJRSosxtzbU

Alan Ridlehoover

November 18, 2022
Tweet

More Decks by Alan Ridlehoover

Other Decks in Programming

Transcript

  1. Alan Ridlehoover & Fito von Zastrow RubyConf Mini 2022 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 coffee class CoffeeMachine

    def vend dispense_cup heat_water prepare_grounds dispense_water dispose_of_grounds end end
  4. ☕ 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
  5. ☕ 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
  6. ☕ 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
  7. ☕ 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
  8. ☕ 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
  9. ☕ 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
  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. ☕ 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
  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. ☕ 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
  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_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
  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_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
  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_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
  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_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
  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_creamer 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: {}) 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
  23. ☕ 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
  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 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
  25. ☕ 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
  26. ☕ 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
  27. ☕ 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
  28. ☕ 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
  29. ☕ 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
  30. ☕ 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
  31. ☕ 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
  32. ☕ 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
  33. ☕ 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
  34. ☕ 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
  35. Yeah, but does it scale? How to apply this to

    a large, complicated code base.
  36. ☕ Plotting complexity vs. churn Pain Free Painful Configuration? Omega

    Mess? Churn Complexity Go Ahead, Make a Mess by Sandi Metz
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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