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

LittleBIGRuby

jeg2
March 13, 2009

 LittleBIGRuby

This was my presentation about what video games can teach us about code reading. I gave it at MountainWest RubyConf 2009.

jeg2

March 13, 2009
Tweet

More Decks by jeg2

Other Decks in Technology

Transcript

  1. JAMES EDWARD GRAY II I wrote two books for the

    Pragmatic Programmers: Best of Ruby Quiz and TextMate: Power Editing for the Mac
  2. JAMES EDWARD GRAY II I wrote two books for the

    Pragmatic Programmers: Best of Ruby Quiz and TextMate: Power Editing for the Mac I’ve contributed documentation and patches for some standard libraries, which I now help to maintain
  3. JAMES EDWARD GRAY II I wrote two books for the

    Pragmatic Programmers: Best of Ruby Quiz and TextMate: Power Editing for the Mac I’ve contributed documentation and patches for some standard libraries, which I now help to maintain I built FasterCSV (now CSV), HighLine (with Greg), Elif, and a few other libraries people don’t use
  4. JAMES EDWARD GRAY II I wrote two books for the

    Pragmatic Programmers: Best of Ruby Quiz and TextMate: Power Editing for the Mac I’ve contributed documentation and patches for some standard libraries, which I now help to maintain I built FasterCSV (now CSV), HighLine (with Greg), Elif, and a few other libraries people don’t use I created the Ruby Quiz and ran it for the first three years
  5. HOW MUCH SHOULD YOU READ? My opinion based on the

    Dreyfus Model of Skill Acquisition.
  6. WHY IS CODE READING IMPORTANT? It can show you common

    idioms It’s good to practice breaking down possibly challenging code, because you will always have to work with other’s code
  7. WHY IS CODE READING IMPORTANT? It can show you common

    idioms It’s good to practice breaking down possibly challenging code, because you will always have to work with other’s code Understanding how something works gives you insight into any limitations it has
  8. WHY IS CODE READING IMPORTANT? It can show you common

    idioms It’s good to practice breaking down possibly challenging code, because you will always have to work with other’s code Understanding how something works gives you insight into any limitations it has Seeing bad code helps you write better code
  9. WHY IS CODE READING IMPORTANT? It can show you common

    idioms It’s good to practice breaking down possibly challenging code, because you will always have to work with other’s code Understanding how something works gives you insight into any limitations it has Seeing bad code helps you write better code Knowledge workers always need more ideas
  10. WHAT IS RESTCLIENT? Sinatra’s sister library (sometimes called “reverse Sinatra”)

    It provides a very clean interface to RESTful web services
  11. WHAT IS RESTCLIENT? Sinatra’s sister library (sometimes called “reverse Sinatra”)

    It provides a very clean interface to RESTful web services Simple well-written code (around 500 lines of clear code for the core functionality)
  12. WHAT IS RESTCLIENT? Sinatra’s sister library (sometimes called “reverse Sinatra”)

    It provides a very clean interface to RESTful web services Simple well-written code (around 500 lines of clear code for the core functionality) Plus a couple of exciting features
  13. BASIC GET Reading tweets with Twitter’s API require "rubygems" require

    "rest_client" require "json" ! twitter = RestClient::Resource.new( "http://twitter.com/statuses", :user => "JEG2", :password => "secret" ) ! json = twitter["friends_timeline.json"].get tweets = JSON.parse(json) tweets.each do |tweet| # ... end
  14. BASIC GET Reading tweets with Twitter’s API require "rubygems" require

    "rest_client" require "json" ! twitter = RestClient::Resource.new( "http://twitter.com/statuses", :user => "JEG2", :password => "secret" ) ! json = twitter["friends_timeline.json"].get tweets = JSON.parse(json) tweets.each do |tweet| # ... end
  15. BASIC POST Posting a tweet with Twitter’s API require "rubygems"

    require "rest_client" require "json" ! twitter = RestClient::Resource.new( "http://twitter.com/statuses", :user => "JEG2", :password => "secret" ) ! json = twitter["update.json"].post(:status => "Hello from #mwrc!") tweet = JSON.parse(json) # ...
  16. BASIC POST Posting a tweet with Twitter’s API require "rubygems"

    require "rest_client" require "json" ! twitter = RestClient::Resource.new( "http://twitter.com/statuses", :user => "JEG2", :password => "secret" ) ! json = twitter["update.json"].post(:status => "Hello from #mwrc!") tweet = JSON.parse(json) # ...
  17. def process_result(res) if res.code =~ /\A2\d{2}\z/ decode res['content-encoding'], res.body if

    res.body elsif %w(301 302 303).include? res.code url = res.header['Location'] ! if url !~ /^http/ uri = URI.parse(@url) uri.path = "/#{url}".squeeze('/') url = uri.to_s end ! raise Redirect, url elsif res.code == "304" raise NotModified, res elsif res.code == "401" raise Unauthorized, res elsif res.code == "404" raise ResourceNotFound, res else raise RequestFailed, res end end def transmit(uri, req, payload) setup_credentials(req) ! net = net_http_class.new(uri.host, uri.port) net.use_ssl = uri.is_a?(URI::HTTPS) net.verify_mode = OpenSSL::SSL::VERIFY_NONE net.read_timeout = @timeout if @timeout net.open_timeout = @open_timeout if @open_timeout ! display_log request_log ! net.start do |http| res = http.request(req, payload) display_log response_log(res) string = process_result(res) ! if string or @method == :head Response.new(string, res) else nil end end rescue EOFError raise RestClient::ServerBrokeConnection rescue Timeout::Error raise RestClient::RequestTimeout end def decode(content_encoding, body) if content_encoding == 'gzip' and not body.empty? Zlib::GzipReader.new(StringIO.new(body)).read elsif content_encoding == 'deflate' Zlib::Inflate.new.inflate(body) else body end end
  18. CURL-ISH REQUESTS Fetching the latest tweet from Twitter’s API $

    restclient \ > get http://twitter.com/statuses/friends_timeline.json?count=1 \ > JEG2 secret [{"text":"Sent out first round of Twitter client betas…", "user":{"name":"Jeremy McAnally","screen_name":"jeremymcanally",…}, …}]
  19. RESTFUL IRB Interacting with the Twitter API $ restclient http://twitter.com/statuses

    JEG2 secret >> post "update.json", :status => "The RestClient shell is fun." => "{\"text\":\"The RestClient shell is fun.\",…}" >> get "friends_timeline.json?count=1" => "[{\"text\":\"The RestClient shell is fun.\",…}]"
  20. RESTFUL IRB Interacting with the Twitter API $ restclient http://twitter.com/statuses

    JEG2 secret >> post "update.json", :status => "The RestClient shell is fun." => "{\"text\":\"The RestClient shell is fun.\",…}" >> get "friends_timeline.json?count=1" => "[{\"text\":\"The RestClient shell is fun.\",…}]"
  21. RESTFUL IRB Interacting with the Twitter API $ restclient http://twitter.com/statuses

    JEG2 secret >> post "update.json", :status => "The RestClient shell is fun." => "{\"text\":\"The RestClient shell is fun.\",…}" >> get "friends_timeline.json?count=1" => "[{\"text\":\"The RestClient shell is fun.\",…}]"
  22. GENERATING RUBY Interactively building a RESTful Ruby script $ RESTCLIENT_LOG=twitter_fun.rb

    restclient … >> post "update.json", :status => "The RestClient shell is fun." => … >> get "friends_timeline.json?count=1" => …
  23. GENERATING RUBY Interactively building a RESTful Ruby script $ RESTCLIENT_LOG=twitter_fun.rb

    restclient … >> post "update.json", :status => "The RestClient shell is fun." => … >> get "friends_timeline.json?count=1" => …
  24. GENERATING RUBY Interactively building a RESTful Ruby script $ RESTCLIENT_LOG=twitter_fun.rb

    restclient … >> post "update.json", :status => "The RestClient shell is fun." => … >> get "friends_timeline.json?count=1" => … # twitter_fun.rb RestClient.post "http://twitter.com/statuses/update.json", "status=The%20RestClient%20shell%20is%20fun.", :content_type=>"application/x-www-form-urlencoded" # => 200 OK | application/json 379 bytes RestClient.get "http://twitter.com/statuses/friends_timeline.json?count=1" # => 200 OK | application/json 381 bytes
  25. THE LESS BORING PARTS OF CSV Tricky data structures are

    needed Rows need ordered access for their columns
  26. THE LESS BORING PARTS OF CSV Tricky data structures are

    needed Rows need ordered access for their columns Users also like to work with them by header name
  27. THE LESS BORING PARTS OF CSV Tricky data structures are

    needed Rows need ordered access for their columns Users also like to work with them by header name Column names often repeat
  28. THE LESS BORING PARTS OF CSV Tricky data structures are

    needed Rows need ordered access for their columns Users also like to work with them by header name Column names often repeat Now that we have m17n, CSV parses in the encoding of your data (no transcoding is done on your data)
  29. CSV::ROW The various ways to refer to data require "csv"

    # using Ruby 1.9 ! data = <<END_DATA Console,Units Sold 2007,Percent,Units Sold 2008,Percent Wii,"719,141",49.4%,"1,184,651",49.6% XBox 360,"333,084",22.9%,"743,976",31.1% PlayStation 3,"404,900",27.8%,"459,777",19.3% END_DATA ps3 = CSV.parse(data, :headers => true, :header_converters => :symbol)[-1] ! ps3[0] # => "PlayStation 3" ps3[:percent] # => "27.8%" ps3[:percent, 3] # => "19.3%" ps3[:percent, ps3.index(:units_sold_2008)] # => "19.3%"
  30. CSV::ROW The various ways to refer to data require "csv"

    # using Ruby 1.9 ! data = <<END_DATA Console,Units Sold 2007,Percent,Units Sold 2008,Percent Wii,"719,141",49.4%,"1,184,651",49.6% XBox 360,"333,084",22.9%,"743,976",31.1% PlayStation 3,"404,900",27.8%,"459,777",19.3% END_DATA ps3 = CSV.parse(data, :headers => true, :header_converters => :symbol)[-1] ! ps3[0] # => "PlayStation 3" ps3[:percent] # => "27.8%" ps3[:percent, 3] # => "19.3%" ps3[:percent, ps3.index(:units_sold_2008)] # => "19.3%"
  31. @io = if data.is_a? String then StringIO.new(data) else data end

    @encoding = if @io.respond_to? :internal_encoding @io.internal_encoding || @io.external_encoding elsif @io.is_a? StringIO @io.string.encoding end @encoding ||= Encoding.default_internal || Encoding.default_external
  32. @io = if data.is_a? String then StringIO.new(data) else data end

    @encoding = if @io.respond_to? :internal_encoding @io.internal_encoding || @io.external_encoding elsif @io.is_a? StringIO @io.string.encoding end @encoding ||= Encoding.default_internal || Encoding.default_external
  33. @io = if data.is_a? String then StringIO.new(data) else data end

    @encoding = if @io.respond_to? :internal_encoding @io.internal_encoding || @io.external_encoding elsif @io.is_a? StringIO @io.string.encoding end @encoding ||= Encoding.default_internal || Encoding.default_external def encode_re(*chunks) Regexp.new(encode_str(*chunks)) end ! def encode_str(*chunks) chunks.map { |chunk| chunk.encode(@encoding.name) }.join end
  34. @io = if data.is_a? String then StringIO.new(data) else data end

    @encoding = if @io.respond_to? :internal_encoding @io.internal_encoding || @io.external_encoding elsif @io.is_a? StringIO @io.string.encoding end @encoding ||= Encoding.default_internal || Encoding.default_external def encode_re(*chunks) Regexp.new(encode_str(*chunks)) end ! def encode_str(*chunks) chunks.map { |chunk| chunk.encode(@encoding.name) }.join end
  35. @io = if data.is_a? String then StringIO.new(data) else data end

    @encoding = if @io.respond_to? :internal_encoding @io.internal_encoding || @io.external_encoding elsif @io.is_a? StringIO @io.string.encoding end @encoding ||= Encoding.default_internal || Encoding.default_external sample = read_to_char(1024) sample += read_to_char(1) if sample[-1..-1] == encode_str("\r") and not @io.eof? ! if sample =~ encode_re("\r\n?|\n") @row_sep = $& break end def encode_re(*chunks) Regexp.new(encode_str(*chunks)) end ! def encode_str(*chunks) chunks.map { |chunk| chunk.encode(@encoding.name) }.join end
  36. @io = if data.is_a? String then StringIO.new(data) else data end

    @encoding = if @io.respond_to? :internal_encoding @io.internal_encoding || @io.external_encoding elsif @io.is_a? StringIO @io.string.encoding end @encoding ||= Encoding.default_internal || Encoding.default_external sample = read_to_char(1024) sample += read_to_char(1) if sample[-1..-1] == encode_str("\r") and not @io.eof? ! if sample =~ encode_re("\r\n?|\n") @row_sep = $& break end def encode_re(*chunks) Regexp.new(encode_str(*chunks)) end ! def encode_str(*chunks) chunks.map { |chunk| chunk.encode(@encoding.name) }.join end
  37. OTHER POINTS OF INTEREST The parser Ruby 1.9’s CSV library

    uses primarily one ugly regular expression from Master Regular Expressions
  38. OTHER POINTS OF INTEREST The parser Ruby 1.9’s CSV library

    uses primarily one ugly regular expression from Master Regular Expressions The old FasterCSV has switched to a non-regex parser to dodge some regex engine weaknesses
  39. OTHER POINTS OF INTEREST The parser Ruby 1.9’s CSV library

    uses primarily one ugly regular expression from Master Regular Expressions The old FasterCSV has switched to a non-regex parser to dodge some regex engine weaknesses FasterCSV::Table is another interesting data structure that can work in columns or rows
  40. WHY THESE LIBRARIES? They teach how to build multiprocessing Unix

    software Covers Thread and fork(), apart and together
  41. WHY THESE LIBRARIES? They teach how to build multiprocessing Unix

    software Covers Thread and fork(), apart and together Using pipes
  42. WHY THESE LIBRARIES? They teach how to build multiprocessing Unix

    software Covers Thread and fork(), apart and together Using pipes Signal handling
  43. WHY THESE LIBRARIES? They teach how to build multiprocessing Unix

    software Covers Thread and fork(), apart and together Using pipes Signal handling And much more
  44. WHY THESE LIBRARIES? They teach how to build multiprocessing Unix

    software Covers Thread and fork(), apart and together Using pipes Signal handling And much more Robust code written by an expert
  45. def terminate options = {}, &block options = { :seconds

    => Float(options).to_i } unless Hash === options ! seconds = getopt :seconds, options trap = getopt :trap, options, lambda{ eval("raise(::Terminator::Error, '#{ seconds }s')", block) } ! handler = Signal.trap(signal, &trap) ! plot_to_kill pid, :in => seconds, :with => signal ! begin block.call ensure Signal.trap(signal, handler) end end
  46. def terminate options = {}, &block options = { :seconds

    => Float(options).to_i } unless Hash === options ! seconds = getopt :seconds, options trap = getopt :trap, options, lambda{ eval("raise(::Terminator::Error, '#{ seconds }s')", block) } ! handler = Signal.trap(signal, &trap) ! plot_to_kill pid, :in => seconds, :with => signal ! begin block.call ensure Signal.trap(signal, handler) end end
  47. def plot_to_kill pid, options = {} seconds = getopt :in,

    options signal = getopt :with, options process.puts [pid, seconds, signal].join(' ') process.flush end
  48. def plot_to_kill pid, options = {} seconds = getopt :in,

    options signal = getopt :with, options process.puts [pid, seconds, signal].join(' ') process.flush end
  49. def plot_to_kill pid, options = {} seconds = getopt :in,

    options signal = getopt :with, options process.puts [pid, seconds, signal].join(' ') process.flush end fattr :process do process = IO.popen "#{ ruby } #{ program.inspect }", 'w+' at_exit do begin Process.kill -9, process.pid rescue Object end end process.sync = true process end
  50. def plot_to_kill pid, options = {} seconds = getopt :in,

    options signal = getopt :with, options process.puts [pid, seconds, signal].join(' ') process.flush end fattr :process do process = IO.popen "#{ ruby } #{ program.inspect }", 'w+' at_exit do begin Process.kill -9, process.pid rescue Object end end process.sync = true process end
  51. fattr :program do code = <<-code while(( line = STDIN.gets

    )) pid, seconds, signal, *ignored = line.strip.split ! pid = Float(pid).to_i seconds = Float(seconds) signal = Float(signal).to_i rescue String(signal) ! sleep seconds ! begin Process.kill signal, pid rescue Object end end code tmp = Tempfile.new "#{ ppid }-#{ pid }-#{ rand }" tmp.write code tmp.close tmp.path end
  52. fattr :program do code = <<-code while(( line = STDIN.gets

    )) pid, seconds, signal, *ignored = line.strip.split ! pid = Float(pid).to_i seconds = Float(seconds) signal = Float(signal).to_i rescue String(signal) ! sleep seconds ! begin Process.kill signal, pid rescue Object end end code tmp = Tempfile.new "#{ ppid }-#{ pid }-#{ rand }" tmp.write code tmp.close tmp.path end
  53. fattr :program do code = <<-code while(( line = STDIN.gets

    )) pid, seconds, signal, *ignored = line.strip.split ! pid = Float(pid).to_i seconds = Float(seconds) signal = Float(signal).to_i rescue String(signal) ! sleep seconds ! begin Process.kill signal, pid rescue Object end end code tmp = Tempfile.new "#{ ppid }-#{ pid }-#{ rand }" tmp.write code tmp.close tmp.path end
  54. fattr :program do code = <<-code while(( line = STDIN.gets

    )) pid, seconds, signal, *ignored = line.strip.split ! pid = Float(pid).to_i seconds = Float(seconds) signal = Float(signal).to_i rescue String(signal) ! sleep seconds ! begin Process.kill signal, pid rescue Object end end code tmp = Tempfile.new "#{ ppid }-#{ pid }-#{ rand }" tmp.write code tmp.close tmp.path end
  55. fattr :program do code = <<-code while(( line = STDIN.gets

    )) pid, seconds, signal, *ignored = line.strip.split ! pid = Float(pid).to_i seconds = Float(seconds) signal = Float(signal).to_i rescue String(signal) ! sleep seconds ! begin Process.kill signal, pid rescue Object end end code tmp = Tempfile.new "#{ ppid }-#{ pid }-#{ rand }" tmp.write code tmp.close tmp.path end
  56. fattr :program do code = <<-code while(( line = STDIN.gets

    )) pid, seconds, signal, *ignored = line.strip.split ! pid = Float(pid).to_i seconds = Float(seconds) signal = Float(signal).to_i rescue String(signal) ! sleep seconds ! begin Process.kill signal, pid rescue Object end end code tmp = Tempfile.new "#{ ppid }-#{ pid }-#{ rand }" tmp.write code tmp.close tmp.path end
  57. fattr :program do code = <<-code while(( line = STDIN.gets

    )) pid, seconds, signal, *ignored = line.strip.split ! pid = Float(pid).to_i seconds = Float(seconds) signal = Float(signal).to_i rescue String(signal) ! sleep seconds ! begin Process.kill signal, pid rescue Object end end code tmp = Tempfile.new "#{ ppid }-#{ pid }-#{ rand }" tmp.write code tmp.close tmp.path end
  58. OTHER POINTS OF INTEREST bj – A robust background priority

    queue for Rails Noticing changes from the outside world via signals
  59. OTHER POINTS OF INTEREST bj – A robust background priority

    queue for Rails Noticing changes from the outside world via signals Managing and monitoring an external job
  60. OTHER POINTS OF INTEREST bj – A robust background priority

    queue for Rails Noticing changes from the outside world via signals Managing and monitoring an external job slave – Trivial multiprocessing with built-in IPC
  61. OTHER POINTS OF INTEREST bj – A robust background priority

    queue for Rails Noticing changes from the outside world via signals Managing and monitoring an external job slave – Trivial multiprocessing with built-in IPC How to set up a “heartbeat” between processes
  62. OTHER POINTS OF INTEREST bj – A robust background priority

    queue for Rails Noticing changes from the outside world via signals Managing and monitoring an external job slave – Trivial multiprocessing with built-in IPC How to set up a “heartbeat” between processes How to run DRb over Unix domain sockets
  63. PROCESS TIPS Take a deep breath and relax Not all

    code sucks Don’t start with Rails
  64. PROCESS TIPS Take a deep breath and relax Not all

    code sucks Don’t start with Rails There’s a ton of great stuff in there
  65. PROCESS TIPS Take a deep breath and relax Not all

    code sucks Don’t start with Rails There’s a ton of great stuff in there But it’s a big and complex beast
  66. PROCESS TIPS Take a deep breath and relax Not all

    code sucks Don’t start with Rails There’s a ton of great stuff in there But it’s a big and complex beast Have a goal
  67. GETTING THE CODE gem unpack GEM_NAME Use anonymous VCS access

    to pull a local copy of the code Open it in your standard environment as you would if you were going to edit it
  68. FINDING THINGS Try the conventions first Executables are probably in

    the bin/ directory Look for MyModule::MyClass in lib/my_module/ my_class.rb
  69. FINDING THINGS Try the conventions first Executables are probably in

    the bin/ directory Look for MyModule::MyClass in lib/my_module/ my_class.rb Look for methods in super classes and mixed in modules
  70. FINDING THINGS Try the conventions first Executables are probably in

    the bin/ directory Look for MyModule::MyClass in lib/my_module/ my_class.rb Look for methods in super classes and mixed in modules But remember Ruby is quite dynamic
  71. FINDING THINGS Try the conventions first Executables are probably in

    the bin/ directory Look for MyModule::MyClass in lib/my_module/ my_class.rb Look for methods in super classes and mixed in modules But remember Ruby is quite dynamic Hunt for some “core extensions”
  72. UNDERSTANDING THE CODE Start with the tests/specs if there are

    any Check for an “examples/” directory
  73. UNDERSTANDING THE CODE Start with the tests/specs if there are

    any Check for an “examples/” directory Try to load and play with certain classes in isolation
  74. UNDERSTANDING THE CODE Start with the tests/specs if there are

    any Check for an “examples/” directory Try to load and play with certain classes in isolation irb -r a_class_to_play_with
  75. UNDERSTANDING THE CODE Start with the tests/specs if there are

    any Check for an “examples/” directory Try to load and play with certain classes in isolation irb -r a_class_to_play_with Remember Ruby’s reflection methods, like methods()