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

Following the Path of Programs

Jim Gay
June 01, 2013

Following the Path of Programs

As the responsibility of our applications grows, so too does our code. We'll walk through techniques of object-oriented programming that help clarify our intent and put responsibilities right where we need them.

Jim Gay

June 01, 2013
Tweet

More Decks by Jim Gay

Other Decks in Technology

Transcript

  1. Following the Path
    of Programs
    Jim Gay - @saturnflyer
    ދ݀ʹೖΒͣΜ͹ދࢠΛಘͣ
    Saturday, June 1, 13

    View Slide

  2. If you do not enter
    the tiger’s cage, you
    will not catch its cub
    Saturday, June 1, 13

    View Slide

  3. ދ݀ʹೖΒͣΜ͹ދࢠΛಘͣ
    Saturday, June 1, 13

    View Slide

  4. “How
    CHINESE
    of you”
    @hiro_asari
    Saturday, June 1, 13

    View Slide

  5. ࢲ͸അࣛͰ͢
    Saturday, June 1, 13

    View Slide

  6. ࢲ͸അࣛͰ͢
    Saturday, June 1, 13

    View Slide

  7. github.com/saturnflyer
    @saturnflyer
    clean-ruby.com
    DC RUG @rubyhangout
    Saturday, June 1, 13

    View Slide

  8. Following the Path
    of Programs
    Jim Gay - @saturnflyer
    Saturday, June 1, 13

    View Slide

  9. Guiding the Path
    of Programs
    with Code
    Saturday, June 1, 13

    View Slide

  10. READ > WRITE
    Saturday, June 1, 13

    View Slide

  11. “The best
    documentation is the
    source file”
    — Eddie Kao, 'Code Reading - Learning More
    about Ruby by Reading Ruby Source Code'
    Saturday, June 1, 13

    View Slide

  12. Programs Are
    Shared Memory
    Saturday, June 1, 13

    View Slide

  13. Rules change.
    Procuderes change.
    Saturday, June 1, 13

    View Slide

  14. It’s not what
    an object is
    that matters,
    it’s what it does
    — Sandi Metz,
    Practical Object Orented Design in Ruby
    Saturday, June 1, 13

    View Slide

  15. Data, Context, and
    Interaction
    Created by Trygve Reenskaug and Jim Coplien
    Saturday, June 1, 13

    View Slide

  16. A key, longstanding hallmark
    of a good program is that it
    separates what is stable
    from what changes
    in the interest of
    good maintenance.
    Saturday, June 1, 13

    View Slide

  17. Mental Models
    Saturday, June 1, 13

    View Slide

  18. What it
    is
    What it
    does
    Saturday, June 1, 13

    View Slide

  19. stable changes
    Saturday, June 1, 13

    View Slide

  20. We use 2 models
    Saturday, June 1, 13

    View Slide

  21. We use 2 brains
    Saturday, June 1, 13

    View Slide

  22. Saturday, June 1, 13

    View Slide

  23. details systems
    Saturday, June 1, 13

    View Slide

  24. details interactions
    Saturday, June 1, 13

    View Slide

  25. Account
    Saturday, June 1, 13

    View Slide

  26. transfer
    pay bills
    Account
    Saturday, June 1, 13

    View Slide

  27. Account = Struct.new(:name, :balance)
    checking = Account.new(‘checking’,9000)
    savings = Account.new(‘savings’,5000)
    Saturday, June 1, 13

    View Slide

  28. Account = Struct.new(:name, :balance)
    checking = Account.new(‘checking’,9000)
    savings = Account.new(‘savings’,5000)
    Transfer.new(checking, savings, 1000).execute
    We understand a use case through
    roles and interactions.
    Saturday, June 1, 13

    View Slide

  29. class Transfer
    def initialize(source, destination, amount)
    @source, @destination, @amount = source.extend(Source), destination, amount
    end
    def execute
    source.transfer
    end
    module Source
    def transfer
    self.balance -= amount
    destination.balance += amount
    end
    end
    end
    Saturday, June 1, 13

    View Slide

  30. class Transfer
    def initialize(source, destination, amount)
    @source, @destination, @amount = source.extend(Source), destination, amount
    end
    def execute
    source.transfer
    end
    module Source
    def transfer
    self.balance -= amount
    destination.balance += amount
    end
    end
    end
    Saturday, June 1, 13

    View Slide

  31. class Transfer
    def initialize(source, destination, amount)
    @source, @destination, @amount = source.extend(Source), destination, amount
    end
    def execute
    source.transfer
    end
    module Source
    def transfer
    self.balance -= amount
    destination.balance += amount
    end
    end
    end
    source.extend(Source)
    Saturday, June 1, 13

    View Slide

  32. source.extend(Source)
    Saturday, June 1, 13

    View Slide

  33. source.extend(Source)
    Saturday, June 1, 13

    View Slide

  34. source.extend(Source)
    source
    Account
    Object
    Kernel
    BasicObject
    Saturday, June 1, 13

    View Slide

  35. source
    Account
    Object
    Kernel
    BasicObject
    Saturday, June 1, 13

    View Slide

  36. source
    Account
    Object
    Kernel
    BasicObject
    Source
    source
    Account
    Object
    Kernel
    BasicObject
    Saturday, June 1, 13

    View Slide

  37. Source.new(account)
    source
    Account
    Object
    Kernel
    BasicObject
    Saturday, June 1, 13

    View Slide

  38. Source.new(account)
    source
    Account
    Object
    Kernel
    BasicObject
    Account
    Object
    Kernel
    BasicObject
    Source
    SimpleDelegator
    Delegator
    #
    BasicObject
    Saturday, June 1, 13

    View Slide

  39. Delegation
    Saturday, June 1, 13

    View Slide

  40. self
    Saturday, June 1, 13

    View Slide

  41. self
    Saturday, June 1, 13

    View Slide

  42. self behavior
    Saturday, June 1, 13

    View Slide

  43. self behavior
    SimpleDelegator
    Saturday, June 1, 13

    View Slide

  44. self behavior
    SimpleDelegator
    message
    Saturday, June 1, 13

    View Slide

  45. self behavior
    SimpleDelegator
    message
    m
    ethod_m
    issing
    Saturday, June 1, 13

    View Slide

  46. self self
    SimpleDelegator
    message
    m
    ethod_m
    issing
    Saturday, June 1, 13

    View Slide

  47. self self
    message
    m
    ethod_m
    issing
    Saturday, June 1, 13

    View Slide

  48. self self
    message
    m
    ethod_m
    issing
    Saturday, June 1, 13

    View Slide

  49. self
    self
    message
    Saturday, June 1, 13

    View Slide

  50. self
    self
    self
    message
    message
    Saturday, June 1, 13

    View Slide

  51. self
    self
    self self
    message
    message message
    Saturday, June 1, 13

    View Slide

  52. self
    Saturday, June 1, 13

    View Slide

  53. self
    Saturday, June 1, 13

    View Slide

  54. self
    Module
    Saturday, June 1, 13

    View Slide

  55. self
    Module
    message
    Saturday, June 1, 13

    View Slide

  56. self
    Module
    message
    Saturday, June 1, 13

    View Slide

  57. self
    message
    Saturday, June 1, 13

    View Slide

  58. self
    message
    Saturday, June 1, 13

    View Slide

  59. self
    message
    Saturday, June 1, 13

    View Slide

  60. Context
    Saturday, June 1, 13

    View Slide

  61. source
    amount
    destination
    Context
    Saturday, June 1, 13

    View Slide

  62. source
    amount
    destination
    Context
    account
    account
    1000
    Saturday, June 1, 13

    View Slide

  63. source
    amount
    destination
    Context
    Saturday, June 1, 13

    View Slide

  64. source
    amount
    destination
    Context
    account
    account
    1000
    Saturday, June 1, 13

    View Slide

  65. Temporary Behavior
    Saturday, June 1, 13

    View Slide

  66. gem install casting
    Saturday, June 1, 13

    View Slide

  67. class User
    include Casting::Client
    end
    user = User.find(3) #
    module Greeting
    def hello_world
    “Hello, from #{@name}”
    end
    end
    user.delegate(:hello_world, Greeting) #=> “Hello, from @saturnflyer”
    user.hello_world #=> NoMethodError
    Saturday, June 1, 13

    View Slide

  68. class User
    include Casting::Client
    end
    user = User.find(3) #
    module Greeting
    def hello_world
    “Hello, from #{self.name}”
    end
    end
    user.delegate(:hello_world, Greeting) #=> “Hello, from @saturnflyer”
    user.hello_world #=> NoMethodError
    Saturday, June 1, 13

    View Slide

  69. class User
    include Casting::Client
    end
    user = User.find(3) #
    module Greeting
    def hello_world
    “Hello, from #{self}”
    end
    end
    user.delegate(:hello_world, Greeting) #=> “Hello, from #user.hello_world #=> NoMethodError
    Saturday, June 1, 13

    View Slide

  70. # Ruby 2.0
    Greeting.instance_method(:hello_world).bind(user).call
    # Ruby 1.9 trick
    user.clone.extend(Greeting).method(:hello_world).unbind.bind(user).call
    self remains the same object
    Saturday, June 1, 13

    View Slide

  71. class User
    include Casting::Client
    delegate_missing_methods
    end
    user.hello_world #=> NoMethodError
    Casting.delegating(user => Greeting) do
    user.hello_world #=> “Hello, from @saturnflyer”
    end
    user.hello_world #=> NoMethodError
    Saturday, June 1, 13

    View Slide

  72. class User
    include Casting::Client
    delegate_missing_methods
    end
    user.hello_world #=> NoMethodError
    Casting.delegating(user => Greeting) do
    user.hello_world #=> “Hello, from @saturnflyer”
    Casting.delegating(user => FormalGreeting) do
    user.hello_world #=> “Good day to you! from @saturnflyer”
    end
    end
    user.hello_world #=> NoMethodError
    Saturday, June 1, 13

    View Slide

  73. class User
    include Casting::Client
    delegate_missing_methods
    end
    user.hello_world #=> NoMethodError
    user.cast_as(Greeting)
    user.hello_world #=> “Hello, from @saturnflyer”
    user.hello_world #=> “Hello, from @saturnflyer”
    user.uncast
    user.hello_world #=> NoMethodError
    Saturday, June 1, 13

    View Slide

  74. class User
    include Casting::Client
    delegate_missing_methods
    end
    class UsersController < ApplicationController
    def show
    respond_with user.cast_as(UserRepresenter)
    end
    end
    Saturday, June 1, 13

    View Slide

  75. travis-ci.org/saturnflyer/casting
    Saturday, June 1, 13

    View Slide

  76. Contextual Behavior
    Low Coupling
    High Cohesion
    Better Encapsulation
    Supports Our System Mental Model
    Separates Our Detail Mental Model
    Saturday, June 1, 13

    View Slide

  77. class Transfer
    def initialize(source, destination, amount)
    @source, @destination, @amount = source.extend(Source), destination, amount
    end
    def execute
    source.transfer
    end
    module Source
    def transfer
    self.balance -= amount
    destination.balance += amount
    end
    end
    private_constant :Source
    end
    Saturday, June 1, 13

    View Slide

  78. SAND
    BOX
    Saturday, June 1, 13

    View Slide

  79. Prepare for
    the future
    Saturday, June 1, 13

    View Slide

  80. Stop worrying and
    focus on interaction
    Saturday, June 1, 13

    View Slide

  81. Thank you!
    @saturnflyer
    github.com/saturnflyer/casting
    KAIGI20PERCENT == 20% off #=> clean-ruby.com
    Saturday, June 1, 13

    View Slide

  82. Reimplementing
    Delegate.rb
    Saturday, June 1, 13

    View Slide

  83. Wrapper vs. Module
    SimpleDelegator.new(object)
    vs.
    object.cast_as(Module)
    Saturday, June 1, 13

    View Slide

  84. class Delegator < BasicObject
    kernel = ::Kernel.dup
    kernel.class_eval do
    [:to_s,:inspect,:=~,:!~,:===,:<=>,:eql?,:hash].each do |m|
    undef_method m
    end
    end
    include kernel
    def self.const_missing(n)
    ::Object.const_get(n)
    end
    def initialize(obj)
    __setobj__(obj)
    Saturday, June 1, 13

    View Slide

  85. class Delegator < BasicObject
    def self.const_missing(n)
    ::Object.const_get(n)
    end
    def initialize(obj)
    __setobj__(obj)
    end
    def method_missing(m, *args, &block)
    target = self.__getobj__
    begin
    target.respond_to?(m) ? target.__send__(m, *args, &block) : super(m, *args, &block
    ensure
    [email protected]_if {|t| %r"\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:"o =~ t} if [email protected]
    Saturday, June 1, 13

    View Slide

  86. class Delegator < BasicObject
    def initialize(obj)
    __setobj__(obj)
    end
    def method_missing(m, *args, &block)
    target = self.__getobj__
    begin
    target.respond_to?(m) ? target.__send__(m, *args, &block) : super(m, *args, &block
    ensure
    [email protected]_if {|t| %r"\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:"o =~ t} if [email protected]
    end
    end
    def respond_to_missing?(m, include_private)
    Saturday, June 1, 13

    View Slide

  87. class Delegator < BasicObject
    def method_missing(m, *args, &block)
    target = self.__getobj__
    begin
    target.respond_to?(m) ? target.__send__(m, *args, &block) : super(m, *args, &b
    ensure
    [email protected]_if {|t| %r"\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:"o =~ t} if [email protected]
    end
    end
    def respond_to_missing?(m, include_private)
    r = self.__getobj__.respond_to?(m, include_private)
    if r && include_private && !self.__getobj__.respond_to?(m, false)
    warn "#{caller(3)[0]}: delegator does not forward private method \##{m}"
    return false
    end
    Saturday, June 1, 13

    View Slide

  88. class Delegator < BasicObject
    def respond_to_missing?(m, include_private)
    r = self.__getobj__.respond_to?(m, include_private)
    if r && include_private && !self.__getobj__.respond_to?(m, false)
    warn "#{caller(3)[0]}: delegator does not forward private method \##{m}"
    return false
    end
    r
    end
    def methods(all=true)
    __getobj__.methods(all) | super
    end
    def public_methods(all=true)
    Saturday, June 1, 13

    View Slide

  89. class Delegator < BasicObject
    def methods(all=true)
    __getobj__.methods(all) | super
    end
    def public_methods(all=true)
    __getobj__.public_methods(all) | super
    end
    def protected_methods(all=true)
    __getobj__.protected_methods(all) | super
    end
    def ==(obj)
    return true if obj.equal?(self)
    Saturday, June 1, 13

    View Slide

  90. class Delegator < BasicObject
    def ==(obj)
    return true if obj.equal?(self)
    self.__getobj__ == obj
    end
    def !=(obj)
    return false if obj.equal?(self)
    __getobj__ != obj
    end
    def !
    !__getobj__
    end
    Saturday, June 1, 13

    View Slide

  91. class Delegator < BasicObject
    def __getobj__
    raise NotImplementedError, "need to define `__getobj__'"
    end
    def __setobj__(obj)
    raise NotImplementedError, "need to define `__setobj__'"
    end
    def marshal_dump
    ivars = instance_variables.reject {|var| /\[email protected]_/ =~ var}
    [
    :__v2__,
    ivars, ivars.map{|var| instance_variable_get(var)},
    __getobj__
    ]
    Saturday, June 1, 13

    View Slide

  92. class Delegator < BasicObject
    def marshal_dump
    ivars = instance_variables.reject {|var| /\[email protected]_/ =~ var}
    [
    :__v2__,
    ivars, ivars.map{|var| instance_variable_get(var)},
    __getobj__
    ]
    end
    def marshal_load(data)
    version, vars, values, obj = data
    if version == :__v2__
    vars.each_with_index{|var, i| instance_variable_set(var, values[i])}
    __setobj__(obj)
    else
    Saturday, June 1, 13

    View Slide

  93. class Delegator < BasicObject
    def marshal_load(data)
    version, vars, values, obj = data
    if version == :__v2__
    vars.each_with_index{|var, i| instance_variable_set(var, values[i])}
    __setobj__(obj)
    else
    __setobj__(data)
    end
    end
    def initialize_clone(obj)
    self.__setobj__(obj.__getobj__.clone)
    end
    def initialize_dup(obj)
    self.__setobj__(obj.__getobj__.dup)
    Saturday, June 1, 13

    View Slide

  94. class Delegator < BasicObject
    def initialize_clone(obj)
    self.__setobj__(obj.__getobj__.clone)
    end
    def initialize_dup(obj)
    self.__setobj__(obj.__getobj__.dup)
    end
    private :initialize_clone, :initialize_dup
    [:trust, :untrust, :taint, :untaint, :freeze].each do |method|
    define_method method do
    __getobj__.send(method)
    super()
    end
    end
    Saturday, June 1, 13

    View Slide

  95. class Delegator < BasicObject
    [:trust, :untrust, :taint, :untaint, :freeze].each do |method|
    define_method method do
    __getobj__.send(method)
    super()
    end
    end
    @delegator_api = self.public_instance_methods
    def self.public_api
    @delegator_api
    end
    end
    Saturday, June 1, 13

    View Slide

  96. class Delegator < BasicObject
    end
    Saturday, June 1, 13

    View Slide

  97. Thank you!
    @saturnflyer
    github.com/saturnflyer/casting
    KAIGI20PERCENT == 20% off #=> clean-ruby.com
    Saturday, June 1, 13

    View Slide