little big ruby

43
Code Reading 101

Upload: littlebigruby

Post on 10-May-2015

2.098 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: Little Big Ruby

Code Reading 101

Page 2: Little Big Ruby

TWO THINGS TO KNOW ABOUT ME

I wrote the TextMate book

My name is Jim Weirich

Page 3: Little Big Ruby

JAMES EDWARDGRAY 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

Page 4: Little Big Ruby
Page 5: Little Big Ruby
Page 6: Little Big Ruby

HI. I’M JAMES ANDI READ CODE.

Page 7: Little Big Ruby

HOW MUCH SHOULD YOU READ?

0% 25% 50% 75% 100%Novice

Advanced Beginner

Competent

Proficient

Expert

My opinion based on the Dreyfus Model of Skill Acquisition.

Page 8: Little Big Ruby

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

Page 9: Little Big Ruby
Page 10: Little Big Ruby

INTRODUCING RESTCLIENT

Page 11: Little Big Ruby

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

Page 12: Little Big Ruby

BASIC GETReading 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"].gettweets = JSON.parse(json)tweets.each do |tweet| # ...end

Page 13: Little Big Ruby

BASIC POSTPosting 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)# ...

Page 14: Little Big Ruby

NETWORKING CODE DONE RIGHT

Page 15: Little Big Ruby

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 endend

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 endrescue EOFError raise RestClient::ServerBrokeConnectionrescue Timeout::Error raise RestClient::RequestTimeoutend

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 endend

Page 16: Little Big Ruby

A RESTFUL SHELL (IN 90 LOC)

Page 17: Little Big Ruby

CURL-ISH REQUESTSFetching the latest tweet !om 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",…}, …}]

Page 18: Little Big Ruby

RESTFUL IRBInteracting 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.\",…}]"

Page 19: Little Big Ruby

LOGGING IN RUBY

Page 20: Little Big Ruby

GENERATING RUBYInteractively 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.rbRestClient.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 bytesRestClient.get "http://twitter.com/statuses/friends_timeline.json?count=1"# => 200 OK | application/json 381 bytes

Page 21: Little Big Ruby
Page 22: Little Big Ruby

FASTERCSV ISTHE NEW CSV

Page 23: Little Big Ruby

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)

Page 24: Little Big Ruby

THE ARRAY-HASH-WITH-DUPLICATES

DATA THING

Page 25: Little Big Ruby

CSV::ROWThe various ways to refer to data

require "csv" # using Ruby 1.9

data = <<END_DATAConsole,Units Sold 2007,Percent,Units Sold 2008,PercentWii,"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_DATAps3 = 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%"

Page 26: Little Big Ruby

M17N IN ACTION

Page 27: Little Big Ruby

@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 = $& breakend

def encode_re(*chunks) Regexp.new(encode_str(*chunks))end

def encode_str(*chunks) chunks.map { |chunk| chunk.encode(@encoding.name) }.joinend

Page 28: Little Big Ruby

OTHER POINTSOF 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

Page 29: Little Big Ruby
Page 30: Little Big Ruby

BJ, SLAVE,AND TERMINATOR

Page 31: Little Big Ruby

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

Page 32: Little Big Ruby

HOW TO ASK YOUR CHILD TO KILL YOU

Page 33: Little Big Ruby

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

Page 34: Little Big Ruby

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

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 processend

Page 35: Little Big Ruby

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

Page 36: Little Big Ruby

OTHER POINTSOF 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

Page 37: Little Big Ruby
Page 38: Little Big Ruby

THE ART OFCODE READING

Page 39: Little Big Ruby

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

Page 40: Little Big Ruby

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

Page 41: Little Big Ruby

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”

Page 42: Little Big Ruby

UNDERSTANDINGTHE 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()

Page 43: Little Big Ruby

QUESTIONS?About code or other important topics…