Making Little Classes out of Big Ones

Making Little Classes out of Big Ones

A Lunch & Learn talk I gave at Hashrocket, talking about the pros & cons of four different tehniques for breaking too-big classes into smaller pieces.

4dea430d31b993abaf41cd9b54f8128d?s=128

avdi

May 23, 2012
Tweet

Transcript

  1. Hashrocket Lunch & Learn Avdi Grimm 2012-04-17

  2. One class to rule them all class User < ActiveRecord::Base

    # social media-related methods def find_friends; ...; end def spam_social_networks(message); end def recent_status; ...; end # authorization-related methods... # account-related methods... # content-related methods # A hojillion more methods... end
  3. Contains social-specific fields create_table :users do |u| t.string :twitter_handle t.string

    :facebook_id t.string :linkedin_id # ... end
  4. Refactoring Options Extract Module Extract Decorator Extract Role Composition

  5. Extract Module class User < ActiveRecord::Base include Sociability # authorization-related

    methods... # account-related methods... # content-related methods # A hojillion more methods... end
  6. Extract Module module Sociability def find_friends; ...; end def spam_social_networks(message);

    end def recent_status; ...; end end
  7. Now you have two problems The computer doesn’t care how

    big your class is But you have limited headspace Out of sight is not out of mind Just pushing the problem around. Better to keep the pain obvious!
  8. Extract Decorator require ’delegate’ class SociableUser < DelegateClass(User) # social

    media-related methods def find_friends; ...; end def spam_social_networks(message); end def recent_status; ...; end end
  9. Extract Decorator # ... @user = SociableUser.new(User.find(params[ :id ])) @user.spam_social_networks(

    "..." ) # ...
  10. Decorator Advantages Has its own private namespace for ivars Easy

    to test in isolation Respects public API of wrapped object Can be used anywhere original was used
  11. Decorator Problems Tough to make perfectly interchangeable original = User.find(id)

    wrapped = SociableUser.new(user) user.class # => SociableUser original.object_id == wrapped.object_id # => false Wrapped object can’t use decorated methods
  12. Wrapped object can’t use decorated methods class User < ActiveRecord::Base

    def bio "Just another user..." end def summary " #{ name } : #{ bio } " end end
  13. Wrapped object can’t use decorated methods class SociableUser < DelegateClass(User)

    def bio bio_from_twitter end end
  14. Wrapped object can’t use decorated methods wrapped_user.bio # => "80%

    angel, 10% daemon, [...]." wrapped_user.summary # => "Avdi: Just another user..."
  15. Extract Role module Sociability def find_friends; ...; end def spam_social_networks(message);

    end def recent_status; ...; end end
  16. Extract Role user = User.find(params[ :id ]) user.extend(Sociability) friends =

    user.find_friends
  17. Extracted Role Advantages Only added where needed, unlike included module

    Object “sees” updated methods Can “re-wire” the object from within
  18. Extracted Role Problems Can “re-wire the object from within No

    isolation: can depend on private methods No private variable namespace Can’t be undone to a gvien object May have a performance penalty
  19. Composition class SocialUser def initialize(data) @data = data end def

    find_friends; ...; end def spam_social_networks(message); end def recent_status; ...; end end
  20. Composition class User < ActiveRecord::Base def social_user SocialUser.new(self) end delegate

    :find_friends , :spam_social_networks , :to => :social_user end
  21. Composition Notes A circular arrangement User delegates management of twitter_handle,

    facebook_id, etc. fields to social_user. SocialUser uses its @user reference only as a data object for those fields.
  22. Breaking the circle social_user creates a seam at which further

    separation can be performed.
  23. Breaking the circle create_table :social_users do |t| t.string :twitter_handle t.string

    :facebook_id t.string :linkedin_id end
  24. Breaking the circle class User < ActiveRecord::Base has_one :social_user delegate

    :find_friends , :spam_social_networks , :to => :social_user end
  25. Composition Advantages Easy to understand Easy to test Can be

    introduced with no change to external usage A gateway to bigger refactorings
  26. Composition Problems Outer class must know about inner

  27. Guidelines Use module inclusion for internal, cross-cutting concerns. E.g. Loggable.

    Use decoration for behavior layered “on top” (e.g. presentation) Use decoration if your extension has its own state. Use module extension for dynamically building up an object with several variable aspects.
  28. Guidelines Use module extension as a cleaner form of monkeypatching.

    Prefer decoration to module extension. Use composition for spinning off distinct responsibilities into separate objects. ???