Following the Path of Programs

09477c358c5897d44121a248326e16d7?s=47 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.

09477c358c5897d44121a248326e16d7?s=128

Jim Gay

June 01, 2013
Tweet

Transcript

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

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

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

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

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

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

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

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

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

    13
  10. READ > WRITE Saturday, June 1, 13

  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
  12. Programs Are Shared Memory Saturday, June 1, 13

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

  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
  15. Data, Context, and Interaction Created by Trygve Reenskaug and Jim

    Coplien Saturday, June 1, 13
  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
  17. Mental Models Saturday, June 1, 13

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

  19. stable changes Saturday, June 1, 13

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

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

  22. Saturday, June 1, 13

  23. details systems Saturday, June 1, 13

  24. details interactions Saturday, June 1, 13

  25. Account Saturday, June 1, 13

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

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

    Saturday, June 1, 13
  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
  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
  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
  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
  32. source.extend(Source) Saturday, June 1, 13

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

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

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

  36. source Account Object Kernel BasicObject Source source Account Object Kernel

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

  38. Source.new(account) source Account Object Kernel BasicObject Account Object Kernel BasicObject

    Source SimpleDelegator Delegator #<Module> BasicObject Saturday, June 1, 13
  39. Delegation Saturday, June 1, 13

  40. self Saturday, June 1, 13

  41. self Saturday, June 1, 13

  42. self behavior Saturday, June 1, 13

  43. self behavior SimpleDelegator Saturday, June 1, 13

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

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

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

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

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

  49. self self message Saturday, June 1, 13

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

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

    13
  52. self Saturday, June 1, 13

  53. self Saturday, June 1, 13

  54. self Module Saturday, June 1, 13

  55. self Module message Saturday, June 1, 13

  56. self Module message Saturday, June 1, 13

  57. self message Saturday, June 1, 13

  58. self message Saturday, June 1, 13

  59. self message Saturday, June 1, 13

  60. Context Saturday, June 1, 13

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

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

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

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

    13
  65. Temporary Behavior Saturday, June 1, 13

  66. gem install casting Saturday, June 1, 13

  67. class User include Casting::Client end user = User.find(3) #<User:0x007fc673393e88 @name=”@saturnflyer”>

    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
  68. class User include Casting::Client end user = User.find(3) #<User:0x007fc673393e88 @name=”@saturnflyer”>

    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
  69. class User include Casting::Client end user = User.find(3) #<User:0x007fc673393e88 @name=”@saturnflyer”>

    module Greeting def hello_world “Hello, from #{self}” end end user.delegate(:hello_world, Greeting) #=> “Hello, from #<User:0x007f8d4b02d9 user.hello_world #=> NoMethodError Saturday, June 1, 13
  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
  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
  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
  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
  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
  75. travis-ci.org/saturnflyer/casting Saturday, June 1, 13

  76. Contextual Behavior Low Coupling High Cohesion Better Encapsulation Supports Our

    System Mental Model Separates Our Detail Mental Model Saturday, June 1, 13
  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
  78. SAND BOX Saturday, June 1, 13

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

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

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

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

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

  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
  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 $@.delete_if {|t| %r"\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:"o =~ t} if $@ Saturday, June 1, 13
  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 $@.delete_if {|t| %r"\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:"o =~ t} if $@ end end def respond_to_missing?(m, include_private) Saturday, June 1, 13
  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 $@.delete_if {|t| %r"\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:"o =~ t} if $@ 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
  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
  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
  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
  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| /\A@delegate_/ =~ var} [ :__v2__, ivars, ivars.map{|var| instance_variable_get(var)}, __getobj__ ] Saturday, June 1, 13
  92. class Delegator < BasicObject def marshal_dump ivars = instance_variables.reject {|var|

    /\A@delegate_/ =~ 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
  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
  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
  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
  96. class Delegator < BasicObject end Saturday, June 1, 13

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

    Saturday, June 1, 13