$30 off During Our Annual Pro Sale. View Details »

Making Little Classes out of Big Ones

avdi
May 23, 2012

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.

avdi

May 23, 2012
Tweet

More Decks by avdi

Other Decks in Programming

Transcript

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

    View Slide

  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

    View Slide

  3. Contains social-specific fields
    create_table :users do |u|
    t.string :twitter_handle
    t.string :facebook_id
    t.string :linkedin_id
    # ...
    end

    View Slide

  4. Refactoring Options
    Extract Module
    Extract Decorator
    Extract Role
    Composition

    View Slide

  5. Extract Module
    class User < ActiveRecord::Base
    include Sociability
    # authorization-related methods...
    # account-related methods...
    # content-related methods
    # A hojillion more methods...
    end

    View Slide

  6. Extract Module
    module Sociability
    def find_friends; ...; end
    def spam_social_networks(message); end
    def recent_status; ...; end
    end

    View Slide

  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!

    View Slide

  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

    View Slide

  9. Extract Decorator
    # ...
    @user = SociableUser.new(User.find(params[ :id ]))
    @user.spam_social_networks( "..." )
    # ...

    View Slide

  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

    View Slide

  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

    View Slide

  12. Wrapped object can’t use decorated
    methods
    class User < ActiveRecord::Base
    def bio
    "Just another user..."
    end
    def summary
    " #{ name } : #{ bio } "
    end
    end

    View Slide

  13. Wrapped object can’t use decorated
    methods
    class SociableUser < DelegateClass(User)
    def bio
    bio_from_twitter
    end
    end

    View Slide

  14. Wrapped object can’t use decorated
    methods
    wrapped_user.bio
    # => "80% angel, 10% daemon, [...]."
    wrapped_user.summary
    # => "Avdi: Just another user..."

    View Slide

  15. Extract Role
    module Sociability
    def find_friends; ...; end
    def spam_social_networks(message); end
    def recent_status; ...; end
    end

    View Slide

  16. Extract Role
    user = User.find(params[ :id ])
    user.extend(Sociability)
    friends = user.find_friends

    View Slide

  17. Extracted Role Advantages
    Only added where needed, unlike included module
    Object “sees” updated methods
    Can “re-wire” the object from within

    View Slide

  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

    View Slide

  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

    View Slide

  20. Composition
    class User < ActiveRecord::Base
    def social_user
    SocialUser.new(self)
    end
    delegate :find_friends , :spam_social_networks ,
    :to => :social_user
    end

    View Slide

  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.

    View Slide

  22. Breaking the circle
    social_user creates a seam at which further separation
    can be performed.

    View Slide

  23. Breaking the circle
    create_table :social_users do |t|
    t.string :twitter_handle
    t.string :facebook_id
    t.string :linkedin_id
    end

    View Slide

  24. Breaking the circle
    class User < ActiveRecord::Base
    has_one :social_user
    delegate :find_friends , :spam_social_networks ,
    :to => :social_user
    end

    View Slide

  25. Composition Advantages
    Easy to understand
    Easy to test
    Can be introduced with no change to external usage
    A gateway to bigger refactorings

    View Slide

  26. Composition Problems
    Outer class must know about inner

    View Slide

  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.

    View Slide

  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.
    ???

    View Slide