Code Charcuterie

Code Charcuterie

My talk on Code Charcuterie from RailsBerry

F29327647a9cff5c69618bae420792ea?s=128

Aaron Patterson

June 07, 2012
Tweet

Transcript

  1. ZOMG HAPPY FRIDAY!

  2. None
  3. Rails Barry

  4. Do you have a Mac? Do you have a Wii?

  5. DarwiinRemote

  6. Aaron Patterson

  7. @tenderlove

  8. @tenderlove TWEET TO ME!

  9. aaron.patterson@gmail.com

  10. aaron.patterson@gmail.com EMAIL TO ME!

  11. AT&T, AT&T logo and all AT&T related marks are trademarks

    of AT&T Intellectual Property and/or AT&T affiliated companies.
  12. Señor Software Engineer

  13. ruby-core rails-core

  14. WWFMD?

  15. None
  16. None
  17. None
  18. None
  19. None
  20. None
  21. None
  22. None
  23. None
  24. AWESOME LOGOS

  25. STOP IT

  26. None
  27. None
  28. NOTHING

  29. Code Charcuterie

  30. Amateur Charcuterier (sp?)

  31. None
  32. None
  33. None
  34. None
  35. Tight Coupling?

  36. Setting Expectations

  37. None
  38. Instrumentation Concurrency Attributes

  39. Number of Doubts Chart of Doubts

  40. Number of Doubts Chart of Doubts Few Doubts

  41. Number of Doubts Chart of Doubts Many Doubts

  42. Instrumentation

  43. Publishing ActiveSupport::Notifications.instrument("render") do render :text => "Foo" end

  44. Subscribing events = [] ActiveSupport::Notifications.subscribe("render") do |*e| events << e

    end
  45. def instrument(name, payload={}) started = Time.now begin yield ensure @notifier.publish(name,

    started, Time.now, @id, payload) end end
  46. def instrument(name, payload={}) started = Time.now begin yield ensure @notifier.publish(name,

    started, Time.now, @id, payload) end end Number of Doubts
  47. my time + children

  48. -> sql executed -> view rendered Events Received

  49. -> sql executed -> view rendered Events Received Number of

    Doubts
  50. Missing Information

  51. -> start rendering -> start execute SQL <- finish execute

    SQL -> start execute SQL <- finish execute SQL <- finish rendering
  52. Rendering SQL SQL

  53. class Listener def start(event) end def finish(event) end end listener

    = Listener.new AS::Notifications.subscribe("render", listener)
  54. Evented Publishing def instrument(name, payload={}) @notifier.start(name, @id, payload) begin yield

    ensure @notifier.finish(name, @id, payload) end end
  55. Make it Quack class Timed def initialize(pattern, delegate) @delegate =

    delegate @timestack = Hash.new { |h,id| h[id] = Hash.new { |ids,name| ids[name] = [] } } end def start(name, id, payload) @timestack[id][name].push Time.now end def finish(name, id, payload) started = @timestack[id][name].pop @delegate.call(name, started, Time.now, id, payload) end end
  56. Tiny Factory def subscribe(pattern = nil, listener = Proc.new) if

    listener.respond_to?(:call) subscriber = Timed.new pattern, listener else subscriber = listener end @subscribers << subscriber end
  57. Our Reward

  58. class Indented def initialize @indentation = 0 end def start

    name, id, payload print indent puts "-> #{name} -- #{payload[:virtual_path]}" @indentation += 1 end def finish name, id, payload @indentation -= 1 print indent puts "<- #{name} -- #{payload[:virtual_path]}" end private def indent " " * @indentation end end ActiveSupport::Notifications.notifier.subscribe(nil, Indented.new)
  59. -> process_action.action_controller -- -> render_template.action_view -- -> !render_template.action_view -- sessions/index

    <- !render_template.action_view -- sessions/index <- render_template.action_view -- -> !render_template.action_view -- layouts/application -> render_partial.action_view -- -> !render_template.action_view -- layouts/_navigation <- !render_template.action_view -- layouts/_navigation <- render_partial.action_view -- -> render_partial.action_view -- -> !render_template.action_view -- sessions/_slim_login <- !render_template.action_view -- sessions/_slim_login <- render_partial.action_view -- <- !render_template.action_view -- layouts/application <- process_action.action_controller --
  60. None
  61. ml !render_template.action_view (4.564 ms) virtual_path layouts/application render_partial.action_view (0.346 ms) identifier

    /app/views/layouts/_navigation render_partial.action_view (0.133 ms) identifier /app/views/sessions/_logout.ht !render_template.action_view (0.291 ms) virtual_path layouts/_navigation !render_template.action_view (0.087 ms) virtual_path sessions/_logout _l
  62. tion_controller (0.021 ms) MessagesController index GET /messages process_action.action_controller (95.463 ms)

    controller MessagesController action index method GET path /messages sql.active_record (5.671 ms) sql SELECT "messages"."id" AS t0_r0, "messages"."address_id" AS t0_r1, "messages"."from" AS t0_r2, "messages"."to" AS t0_r3, "messages"."disposable" AS t0_r4, "messages"."subject" AS t0_r5, "messages"."body" AS t0_r6, "messages"."plain" AS t0_r7, "messages"."html" AS t0_r8, "messages"."created_at" AS t0_r9, "messages"."updated_at" AS t0_r10, "messages"."parsed_message_id" AS t0_r11, "addresses"."id" AS t1_r0, "addresses"."name" AS t1_r1, "addresses"."person_id" AS t1_r2, "addresses"."created_at" AS t1_r3, "addresses"."updated_at" AS t1_r4 FROM "messages" LEFT render_template.action_view (63.632 ms) identifier /app/views/messages/index.html layout layouts/application
  63. Concurrency

  64. Amdahl's Law

  65. None
  66. laptop = Laptop.new 1 10.times { laptop.execute { sleep(1) }

    }
  67. None
  68. = 1

  69. $ time ruby test.rb real 0m10.046s user 0m0.023s sys 0m0.008s

    $
  70. laptop = Laptop.new ∞ 10.times { laptop.execute { sleep(1) }

    }
  71. None
  72. = 10

  73. $ time ruby test.rb real 0m1.033s user 0m0.023s sys 0m0.008s

    $
  74. Modeling Our Machine

  75. class Sleeper def run; sleep(1); end end q = Queue.new

    10.times { q.push Sleeper.new } 10.times { q.push nil } # 10 cores! wow! 10.times.map { Thread.new { while obj = q.pop obj.run end } }.each(&:join)
  76. What is my P?

  77. class Sleeper def run; sleep(1); end end q = SlowQueue.new

    # make `pop` cost more 10.times { q.push Sleeper.new } 10.times { q.push nil } # 10 cores! wow! 10.times.map { Thread.new { while obj = q.pop obj.run end } }.each(&:join)
  78. $ time ruby test.rb real 0m2.153s user 0m0.027s sys 0m0.012s

  79. Hidden Sequential Code

  80. Starvation

  81. Scarce Resources

  82. None
  83. ME EBI

  84. ME EBI

  85. Connection Pool

  86. Connections Are Not Thread Safe

  87. One to One

  88. One to One Number of Doubts

  89. pool.connection pool = ActiveRecord::Base.connection_pool p pool.connection.object_id # => 1234 p

    pool.connection.object_id # => 1234
  90. Starvation 5.times { Thread.new { pool.connection } } Thread.new {

    pool.connection }
  91. Starvation 5.times { Thread.new { pool.connection } } Thread.new {

    pool.connection }
  92. Queueing

  93. Splitting Work cpu_queue = Queue.new io_queue = Queue.new 5.times {

    Thread.new { io_queue.pop.run } } 10.times { Thread.new { cpu_queue.pop.run } }
  94. Consumer class Saver def initialize(record) @record = record end def

    run @record.save! end end
  95. Producer class CreatePost def initialize(q) @q = q end def

    run post = Post.new(:name => 'aaron') @q.push SavePost.new(post) end end
  96. None
  97. Parallel CPU def fib n if n < 3 1

    else fib(n-1) + fib(n-2) end end 2.times.map { fib(34) }
  98. $ time ruby test.rb real 0m3.315s user 0m3.264s sys 0m0.010s

  99. 2.times.map { Thread.new { fib(34) } }.each(&:join) Parallelize

  100. Caught in the GIL $ time ruby test.rb real 0m4.825s

    user 0m3.823s sys 0m1.978s
  101. Slow I/O Server server = GenericServer.new('127.0.0.1') server.start do |socket| sleep

    0.5 socket.print "HELLO" socket.close end
  102. TCP Client require 'socket' 5.times.map { sock = TCPSocket.new '127.0.0.1',

    28561 puts sock.read }
  103. $ time ruby client.rb HELLO HELLO HELLO HELLO HELLO real

    0m2.546s user 0m0.026s sys 0m0.009s
  104. require 'socket' 5.times.map { Thread.new { sock = TCPSocket.new '127.0.0.1',

    28561 puts sock.read } }.each(&:join) Parallelize
  105. $ time ruby client.rb HELLO HELLO HELLO HELLO HELLO real

    0m0.552s user 0m0.028s sys 0m0.009s
  106. Sending Email!

  107. class UserController < ApplicationController def create user = User.create!(params[:user]) UserMailer.deliver_welcome_email(user)

    end end
  108. class UserController < ApplicationController def create user = User.create!(params[:user]) UserMailer.deliver_welcome_email(user)

    end end
  109. class UserController < ApplicationController def create user = User.create!(params[:user]) UserMailer.deliver_welcome_email(user)

    end end Number of Doubts
  110. class MailSender def initialize(id) @id = id end def run

    user = User.find(@id) UserMailer.deliver_welcome_email(user) end end
  111. class UserController < ApplicationController def create user = User.create!(params[:user]) mail_queue.push

    MailSender.new(user.id) end end
  112. Thread.new { loop do mail_queue.pop.run } }

  113. None
  114. Outside the loop More Parallel

  115. You are ENCOURAGED to use threads

  116. I/O can be parallelized CPU cannot * in MRI

  117. Attribute Accessors

  118. Attribute Access post = Post.new(:title => 'hello!') post.title # =>

    'hello!' post.respond_to?(:title) # => true
  119. Lazily Defined begin p Post.instance_method(:name) rescue NameError puts "oh no!"

    end Post.new(:name => 'aaron') p Post.instance_method(:name)
  120. Output $ ruby -I lib:test test/cases/argh.rb oh no! #<UnboundMethod: Post(#<Module:0x00>)#__temp__>

  121. Definition Time def method_missing(method, *args, &block) unless self.class.attribute_methods_generated? self.class.define_attribute_methods ...

    end end def respond_to?(name, include_private = false) unless self.class.attribute_methods_generated? self.class.define_attribute_methods end super end
  122. Who called respond_to? {:method=>"name="} ########################################################################################## lib/active_record/attribute_assignment.rb:81:in `block in assign_attributes' lib/active_record/attribute_assignment.rb:78:in

    `each' lib/active_record/attribute_assignment.rb:78:in `assign_attributes' lib/active_record/base.rb:498:in `initialize' test/cases/argh.rb:16:in `new' test/cases/argh.rb:16:in `<main>' ########################################################################################## {:method=>"_run__1290908416815974468__initialize__2045902499019596289__callbacks"} ########################################################################################## lib/active_support/callbacks.rb:398:in `__run_callback' lib/active_support/callbacks.rb:385:in `_run_initialize_callbacks' lib/active_support/callbacks.rb:81:in `run_callbacks' lib/active_record/base.rb:501:in `initialize' test/cases/argh.rb:16:in `new' test/cases/argh.rb:16:in `<main>' ########################################################################################## Number of Doubts
  123. after_initialize class Subject < ActiveRecord::Base after_initialize :set_email_address protected def set_email_address

    ... end end
  124. use super class Subject < ActiveRecord::Base def initialize(*args) super set_email_address

    # after_init end protected def set_email_address ... end end Number of Doubts
  125. DO IT AGAIN! 10.times { Post.new(:name => 'aaron') }

  126. What method? {:method=>"name="} {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"} {:method=>"name="} {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"} {:method=>"name="} {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"} {:method=>"name="} {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"}

    {:method=>"name="} {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"} {:method=>"name="} {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"} {:method=>"name="} {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"} {:method=>"name="} {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"} {:method=>"name="} {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"} {:method=>"name="} {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"} Number of Doubts
  127. Define Methods def define_attribute_methods .... @attribute_methods_mutex.synchronize do return if attribute_methods_generated?

    superclass.define_attribute_methods unless self == base_class super(column_names) column_names.each { |name| define_external_attribute_method(name) } @attribute_methods_generated = true end end
  128. Define Methods def define_attribute_methods .... @attribute_methods_mutex.synchronize do return if attribute_methods_generated?

    superclass.define_attribute_methods unless self == base_class super(column_names) column_names.each { |name| define_external_attribute_method(name) } @attribute_methods_generated = true end end wtf???
  129. RAWWWRRRR class A def hi(stuff) end end class B <

    A def hi super(%w{ problem? }) end end
  130. :'( def calls_hi(obj) obj.hi(%w{ how are you? }) end calls_hi(A.new)

    # => OK! calls_hi(B.new) # => Boom!
  131. IF THEN B is an A B can be used

    as an A
  132. Number of Doubts

  133. define_attribute_methods

  134. ActiveModel

  135. define_attribute_methods def define_attribute_methods(attr_names) attr_names.each { |attr_name| define_attribute_method(attr_name) } end

  136. define_attribute_methods def define_attribute_methods(attr_names) attr_names.each { |attr_name| define_attribute_method(attr_name) } end THANKS

    ACTIVE MODEL
  137. define_attribute_method def define_attribute_method(attr_name) ... unless instance_method_already_implemented?(method_name) generate_method = "define_method_#{matcher.method_missing_target}" if

    respond_to?(generate_method, true) send(generate_method, attr_name) else define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s end end end
  138. define_attribute_method def define_attribute_method(attr_name) ... unless instance_method_already_implemented?(method_name) generate_method = "define_method_#{matcher.method_missing_target}" if

    respond_to?(generate_method, true) send(generate_method, attr_name) else define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s end end end
  139. define_attribute_method def define_attribute_method(attr_name) ... unless instance_method_already_implemented?(method_name) generate_method = "define_method_#{matcher.method_missing_target}" if

    respond_to?(generate_method, true) send(generate_method, attr_name) else define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s end end end
  140. define_attribute_method def define_attribute_method(attr_name) ... unless instance_method_already_implemented?(method_name) generate_method = "define_method_#{matcher.method_missing_target}" if

    respond_to?(generate_method, true) send(generate_method, attr_name) else define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s end end end
  141. define_attribute_method def define_attribute_method(attr_name) ... unless instance_method_already_implemented?(method_name) generate_method = "define_method_#{matcher.method_missing_target}" if

    respond_to?(generate_method, true) send(generate_method, attr_name) else define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s end end end
  142. WHY AREN'T THEY ALL OPTIMIZED????

  143. define_method_attribute define_method_attribute= define_method_attribute_before_type_cast define_method_attribute? define_method_attribute_changed? define_method_attribute_change define_method_attribute_will_change! define_method_attribute_was define_method_reset_attribute! define_method__attribute

    define_method_attribute define_method_attribute= define_method_attribute_before_type_cast define_method_attribute? define_method_attribute_changed? define_method_attribute_change define_method_attribute_will_change! define_method_attribute_was define_method_reset_attribute! define_method__attribute
  144. define_method_attribute define_method_attribute= define_method_attribute_before_type_cast define_method_attribute? define_method_attribute_changed? define_method_attribute_change define_method_attribute_will_change! define_method_attribute_was define_method_reset_attribute! define_method__attribute

    define_method_attribute define_method_attribute= define_method_attribute_before_type_cast define_method_attribute? define_method_attribute_changed? define_method_attribute_change define_method_attribute_will_change! define_method_attribute_was define_method_reset_attribute! define_method__attribute
  145. define_method_attribute define_method_attribute= define_method_attribute_before_type_cast define_method_attribute? define_method_attribute_changed? define_method_attribute_change define_method_attribute_will_change! define_method_attribute_was define_method_reset_attribute! define_method__attribute

    define_method_attribute define_method_attribute= define_method_attribute_before_type_cast define_method_attribute? define_method_attribute_changed? define_method_attribute_change define_method_attribute_will_change! define_method_attribute_was define_method_reset_attribute! define_method__attribute
  146. define_method_attribute define_method_attribute= define_method_attribute_before_type_cast define_method_attribute? define_method_attribute_changed? define_method_attribute_change define_method_attribute_will_change! define_method_attribute_was define_method_reset_attribute! define_method__attribute

    define_method_attribute define_method_attribute= define_method_attribute_before_type_cast define_method_attribute? define_method_attribute_changed? define_method_attribute_change define_method_attribute_will_change! define_method_attribute_was define_method_reset_attribute! define_method__attribute THANKS ACTIVE MODEL
  147. Number of Doubts

  148. Side Note: Does anyone USE this?

  149. Side Note: Should you use this?

  150. Where were we?

  151. Post.new respond_to? AR define_attr_methods AM define_attr_methods AM define_attr_method AR define_method_attr

  152. In VIM •Ctrl-] •Ctrl-o •:help ctags

  153. def define_method_attribute(attr_name) generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def __temp__

    #{internal_attribute_access_code(attr_name, attribute_cast_code(attr_name))} end alias_method '#{attr_name}', :__temp__ undef_method :__temp__ STR end
  154. method(:name).source

  155. Type Cast Value Lookup post.method(:title).source

  156. Post#name def name attr_name = 'name' unless @attributes.has_key?(attr_name) missing_attribute(attr_name, caller)

    end v = @attributes[attr_name] && v end
  157. Post#name def name attr_name = 'name' unless @attributes.has_key?(attr_name) missing_attribute(attr_name, caller)

    end v = @attributes[attr_name] && v end SEEMS LEGIT
  158. Post#created_at def created_at attr_name = 'created_at' unless @attributes.has_key?(attr_name) @attributes_cache[attr_name] ||=

    (missing_attribute(attr_name, caller) end (v=@attributes[attr_name]) && ActiveRecord::ConnectionAdapters::SQLiteColumn.string_to_time(v)) end
  159. Post#created_at def created_at attr_name = 'created_at' unless @attributes.has_key?(attr_name) @attributes_cache[attr_name] ||=

    (missing_attribute(attr_name, caller) end (v=@attributes[attr_name]) && ActiveRecord::ConnectionAdapters::SQLiteColumn.string_to_time(v)) end DOUBTS INCREASING
  160. attribute_cast_code def attribute_cast_code(attr_name) columns_hash[attr_name].type_cast_code('v') end

  161. type_cast_code def type_cast_code(var_name) klass = self.class.name case type when :string,

    :text then var_name when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)" when :float then "#{var_name}.to_f" when :decimal then "#{klass}.value_to_decimal(#{var_name})" when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})" when :time then "#{klass}.string_to_dummy_time(#{var_name})" when :date then "#{klass}.string_to_date(#{var_name})" when :binary then "#{klass}.binary_to_string(#{var_name})" when :boolean then "#{klass}.value_to_boolean(#{var_name})" else var_name end end
  162. type_cast def type_cast(value) return nil if value.nil? return coder.load(value) if

    encoded? klass = self.class case type when :string, :text then value when :integer then value.to_i rescue value ? 1 : 0 when :float then value.to_f when :decimal then klass.value_to_decimal(value) when :datetime, :timestamp then klass.string_to_time(value) when :time then klass.string_to_dummy_time(value) when :date then klass.string_to_date(value) when :binary then klass.binary_to_string(value) when :boolean then klass.value_to_boolean(value) else value end end
  163. Number of Doubts

  164. Number of Doubts TOO MANY!

  165. HOWTO FIX?

  166. What do we want? •Less dynamic code •Consistent method definitions

    •Matching speed (even on read_attribute)
  167. What does it do? •Attribute lookup •Column lookup •Value typecasting

    •Typecast value caching
  168. def read_attribute(attr_name) # If it's cached, just return it @attributes_cache.fetch(attr_name.to_s)

    { |name| column = @columns_hash[name] value = @attributes.fetch(name) { return attribute_missing(name, caller) } # Cache if we're supposed to if self.class.cache_attribute?(name) @attributes_cache[name] = column.type_cast(value) else column.type_cast value end } end
  169. def name read_attribute 'name' end Post#name

  170. def created_at read_attribute 'created_at' end Post#created_at

  171. BONUS FEATURE! Post.select('true as hi').first.hi # => true

  172. None
  173. None
  174. Clean Code IS A FEATURE

  175. Don't throw out the baby with the bathwater!

  176. Questions? ὑ ὑ ὑ ὑ ὑ ὑ