LittleBIGRuby

259f23c3b129f07b0c496b9f0495f07e?s=47 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.

259f23c3b129f07b0c496b9f0495f07e?s=128

jeg2

March 13, 2009
Tweet

Transcript

  1. None
  2. Code Reading 101

  3. TWO THINGS TO KNOW ABOUT ME

  4. TWO THINGS TO KNOW ABOUT ME I wrote the TextMate

    book
  5. TWO THINGS TO KNOW ABOUT ME I wrote the TextMate

    book My name is Jim Weirich
  6. JAMES EDWARD GRAY II

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

    Pragmatic Programmers: Best of Ruby Quiz and TextMate: Power Editing for the Mac
  8. 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
  9. 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
  10. 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
  11. None
  12. None
  13. None
  14. None
  15. HI. I’M JAMES AND I READ CODE.

  16. HOW MUCH SHOULD YOU READ?

  17. HOW MUCH SHOULD YOU READ? My opinion based on the

    Dreyfus Model of Skill Acquisition.
  18. WHY IS CODE READING IMPORTANT?

  19. WHY IS CODE READING IMPORTANT? It can show you common

    idioms
  20. 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
  21. 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
  22. 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
  23. 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
  24. None
  25. None
  26. INTRODUCING RESTCLIENT

  27. WHAT IS RESTCLIENT?

  28. WHAT IS RESTCLIENT? Sinatra’s sister library (sometimes called “reverse Sinatra”)

  29. WHAT IS RESTCLIENT? Sinatra’s sister library (sometimes called “reverse Sinatra”)

    It provides a very clean interface to RESTful web services
  30. 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)
  31. 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
  32. BASIC GET Reading tweets with Twitter’s API

  33. 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
  34. 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
  35. BASIC POST Posting a tweet with Twitter’s API

  36. 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) # ...
  37. 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) # ...
  38. NETWORKING CODE DONE RIGHT

  39. None
  40. 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
  41. A RESTFUL SHELL (IN 90 LOC)

  42. CURL-ISH REQUESTS Fetching the latest tweet from Twitter’s API

  43. 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",…}, …}]
  44. RESTFUL IRB Interacting with the Twitter API

  45. 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.\",…}]"
  46. 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.\",…}]"
  47. 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.\",…}]"
  48. LOGGING IN RUBY

  49. GENERATING RUBY Interactively building a RESTful Ruby script

  50. 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" => …
  51. 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" => …
  52. 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
  53. None
  54. None
  55. FASTERCSV IS THE NEW CSV

  56. THE LESS BORING PARTS OF CSV

  57. THE LESS BORING PARTS OF CSV Tricky data structures are

    needed
  58. THE LESS BORING PARTS OF CSV Tricky data structures are

    needed Rows need ordered access for their columns
  59. 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
  60. 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
  61. 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)
  62. THE ARRAY-HASH- WITH-DUPLICATES DATA THING

  63. CSV::ROW The various ways to refer to data

  64. 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%"
  65. 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%"
  66. M17N IN ACTION

  67. None
  68. @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
  69. @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
  70. @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
  71. @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
  72. @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
  73. @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
  74. OTHER POINTS OF INTEREST

  75. OTHER POINTS OF INTEREST The parser

  76. OTHER POINTS OF INTEREST The parser Ruby 1.9’s CSV library

    uses primarily one ugly regular expression from Master Regular Expressions
  77. 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
  78. 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
  79. None
  80. None
  81. BJ, SLAVE, AND TERMINATOR

  82. WHY THESE LIBRARIES?

  83. WHY THESE LIBRARIES? They teach how to build multiprocessing Unix

    software
  84. WHY THESE LIBRARIES? They teach how to build multiprocessing Unix

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

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

    software Covers Thread and fork(), apart and together Using pipes Signal handling
  87. 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
  88. 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
  89. HOW TO ASK YOUR CHILD TO KILL YOU

  90. None
  91. 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
  92. 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
  93. None
  94. def plot_to_kill pid, options = {} seconds = getopt :in,

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

    options signal = getopt :with, options process.puts [pid, seconds, signal].join(' ') process.flush end
  96. 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
  97. 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
  98. None
  99. 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
  100. 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
  101. 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
  102. 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
  103. 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
  104. 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
  105. 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
  106. OTHER POINTS OF INTEREST

  107. OTHER POINTS OF INTEREST bj – A robust background priority

    queue for Rails
  108. OTHER POINTS OF INTEREST bj – A robust background priority

    queue for Rails Noticing changes from the outside world via signals
  109. 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
  110. 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
  111. 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
  112. 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
  113. None
  114. None
  115. THE ART OF CODE READING

  116. PROCESS TIPS

  117. PROCESS TIPS Take a deep breath and relax

  118. PROCESS TIPS Take a deep breath and relax Not all

    code sucks
  119. PROCESS TIPS Take a deep breath and relax Not all

    code sucks Don’t start with Rails
  120. 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
  121. 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
  122. 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
  123. GETTING THE CODE

  124. GETTING THE CODE gem unpack GEM_NAME

  125. GETTING THE CODE gem unpack GEM_NAME Use anonymous VCS access

    to pull a local copy of the code
  126. 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
  127. FINDING THINGS

  128. FINDING THINGS Try the conventions first

  129. FINDING THINGS Try the conventions first Executables are probably in

    the bin/ directory
  130. FINDING THINGS Try the conventions first Executables are probably in

    the bin/ directory Look for MyModule::MyClass in lib/my_module/ my_class.rb
  131. 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
  132. 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
  133. 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”
  134. UNDERSTANDING THE CODE

  135. UNDERSTANDING THE CODE Start with the tests/specs if there are

    any
  136. UNDERSTANDING THE CODE Start with the tests/specs if there are

    any Check for an “examples/” directory
  137. 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
  138. 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
  139. 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()
  140. QUESTIONS? About code or other important topics…