Cleanliness is Next to Domain-Specificity

67 %
33 %
Information about Cleanliness is Next to Domain-Specificity

Published on November 5, 2007

Author: viget

Source: slideshare.net

Description

Ben Scofield discusses linguistics, DSLs in Ruby, and how writing domain-specific code improves your software

Cleanliness is Next to Domain-Specificity Ben Scofield Senior Developer Viget Labs 4 November 2007 © Copyright 2007 Viget Labs, LLC – www.viget.com

Part 1: Linguistics Part 2: Refactoring

The Ruby Community

http://www.flickr.com/photos/jesper/1395418767/ Interdisciplinary

Linguistics

Categories

Regional Dialects

(more) Regional Dialects

Jargons Cants

Pidgins and Creoles

Vocabulary Grammar

Real DSLs Ruby Domain-Specific Code

ActiveRecord

RSpec

Same Grammar, Different Vocabulary

Who Cares?

DSLs DSL Intimidate and Frighten http://www.flickr.com/photos/cwsteeds/58514985/

Write a Parser? No, Thanks. http://www.flickr.com/photos/rooreynolds/243810988/

http://www.flickr.com/photos/jonosd/498162310/

Change the Vocabulary Change the World Heroes on NBC - Mondays at 9 PM

API vs. Dialect

Why DSanything?

Who Are We?

http://www.oreillynet.com/onlamp/blog/2006/05/ sapirwhorf_is_not_a_klingon.html http://tech.puredanger.com/2006/11/08/does-your-programming- language-affect-how-you-think/ http://snakesgemscoffee.blogspot.com/2006_11_01_archive.html http://talklikeaduck.denhaven2.com/articles/2007/06/11/sapir- whorf http://adams.id.au/blog/2007/10/what-is-behaviour-driven- development/ http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/75914 http://www.weiqigao.com/blog/2007/09/10/ an_interesting_experiment_sapir_whorf_hypothesis.html http://www.ibm.com/developerworks/blogs/page/pmuellr? tag=ruby http://intertwingly.net/blog/2007/10/05/NOC http://brooders.net/category/perl/ http://gilesbowkett.blogspot.com/2007/02/sapir-worf-in- action_19.html http://blogs.msdn.com/daveremy/archive/2005/04/06/ sapirwhorfs.aspx http://erlangish.blogspot.com/2007/05/shape-of-your-mind.html http://www.oreillynet.com/onlamp/blog/2006/06/ how_does_a_programming_languag.html

Linguistic Determinism

The Hopi

Linguistic Relativism

http://www.flickr.com/photos/maxhunter/79993854/ Snow qanuk avalanche kaneq blizzard kanevvluk dusting natquik flurry nevluk frost aniu hail qanikcaq hardpack muruaneq igloo nutaryuk pingo qanisqineq powder qengaruk sleet utvak slush navcaq snow pirta snowflake pirtuk snowstorm ... ...

Color Perception http://www.flickr.com/photos/thedeplorableword/140856437/

Direction of Causality Degree of Influence

RSpec  Sapir-Whorf

Testing is Too Late

Specifications Come First

RSpec Leads You in the Right Direction

DSDs are Built on Linguistic Relativism

Keep Your Head in the Domain

Refactoring

Tastes Vary

?

Finding a Ticket

kayak.com

What Does This Do?

Ruby? Awesome!

exit 0 @@results.each do |r| if searchtype == 'h' puts quot;#{r.price} url=#{r.url}quot; puts quot;#{r.stars} #{r.name} $#{r.loprice} - $#{r.hiprice}quot; elsif searchtype == 'f' puts quot;#{r.price} url=#{r.url}quot; r.legs.each do |leg| puts quot; #{leg}quot; end end end exit(0) more = poll_results_file(searchtype) @@results.each do |r| puts quot;#{r.price} #{r.url}quot; r.legs.each do |leg| puts quot; #{leg}quot; end end end Whoa.

Start at the End

I Want to: find flights from CLT to RDU leaving today and returning in one week

I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7

48 40 The Old Way sid = getsession(@@token) searchid = start_flight_search(sid, ‘n’, ‘CLT’, ‘RDU’, Date.today, nil, 1) more = poll_results('f', sid, searchid, nil) while more == 'true' do more = poll_results('f', sid, searchid, nil) sleep(3) end def poll_results(searchtype, sid, searchid, count) url = quot;/s#{@@sparkleinstance}/apibasic/flight?searchid=#{searchid}&apimode=1&_sid_=#{sid}quot; more = nil Net::HTTP.start(@@hostname, @@port) do |http| if count url += quot;&c=#{count}quot; end response = http.get(url) body = response.body File.open(quot;ksearchbody.xmlquot;, quot;wquot;) do |f| f.puts(body) end more = handle_results(searchtype, body) if more != 'true' # save the body, so we can test without doin # an actual search File.open(quot;ksearchresults.xmlquot;, quot;wquot;) do |f| f.puts(body) end end end return more end

48 40 The Old Way, cont. def handle_results(searchtype, body) xml = REXML::Document.new(body) more = xml.elements['/searchresult/morepending'] @@lastcount = xml.elements['/searchresult/count'].text @@sparkleinstance = xml.elements['/searchresult/searchinstance'].text if more more = more.text end if more != 'true' @@results = [] #puts quot;count=#{@@lastcount}quot; xml.elements.each(quot;/searchresult/trips/tripquot;) do |e| trip = Trip.new() e.each_element(quot;pricequot;) do |t| trip.price = t.text trip.url = t.attribute(quot;urlquot;) end e.each_element(quot;legsquot;) do |legs| legs.each_element(quot;legquot;) do |l| leg = Leg.new l.each_element do |ld| # extract the detail from each leg case when ld.name == 'airline': leg.airlinecode = ld.text #... end end trip.legs << leg end # leg in legs loop end # legs in trip loop #e.each_element(quot;/searchresult/trips/trip/pricequot;) { |p| trip.price = p.text } #puts quot;trip: #{trip.price}quot; @@results << trip end # each trip end return more end

40 Output session_url = quot;/k/ident/apisession?token=#{token}quot; search_url = quot;/s/apisearch?basicmode=true&oneway=n&origin=#{origin} &destination=#{destination}&destcode=&depart_date=#{dep_date} &depart_time=a&return_date=#{ret_date}&return_time=a&travelers=# {travelers}&cabin=e&action=doflights&apimode=1&_sid_=#{sid}quot; results_url = quot;/s#{@@sparkleinstance}/apibasic/flight?searchid=# {searchid}&apimode=1&_sid_=#{sid}quot;

40 Expectations class KayakTest < Test::Unit::TestCase def test_find_should_call_out_to_session_endpoint setup_mocks_for_find Kayak.find :flights end private def setup_mocks_for_find response = mock(:body => '<?xml version=quot;1.0quot;?> <ident> <uid>uid</uid> <sid>0123456789</sid> <token>12345</token> <error></error> </ident>') success = mock() success.expects(:get).with('/k/ident/apisession?token=12345').returns (response) Net::HTTP.expects(:start).at_least_once.yields(success) end end

40 Parsing Responses class KayakTest < Test::Unit::TestCase def test_session_response_should_be_parsed_for_session_id setup_mocks_for_find Kayak.find :flights assert_equal '0123456789', Kayak.session_id end private def setup_mocks_for_find response = mock(:body => '<?xml version=quot;1.0quot;?> <ident> <uid>uid</uid> <sid>0123456789</sid> <token>12345</token> <error></error> </ident>') success = mock() success.expects(:get).with('/k/ident/apisession?token=12345').returns (response) Net::HTTP.expects(:start).at_least_once.yields(success) end end

class Kayak First Cut @@session_id = nil @@search_id = nil @@search_options = {} TOKEN = '12345' HOSTNAME = 'www.kayak.com' PORT = 80 class << self def session_id @@session_id end def method_missing(name, *args) @@search_options[name] end def find(type, conditions = {}) session_id ||= initialize_session @@search_options[:origin] = conditions[:from] @@search_options[:destination] = conditions[:to] @@search_options[:depart_date] = conditions[:leaving].strftime('%m/%d/%Y') if conditions[:leaving] @@search_options[:leave_date] = conditions[:returning].strftime('%m/%d/%Y') if conditions[:returning] search_id ||= initialize_search self end def initialize_search Net::HTTP.start(HOSTNAME, PORT) do |http| response = http.get(quot;/s/apisearch?basicmode=true&oneway=n&destcode=&depart_time=a&... if body = response.body xml = REXML::Document.new(body) @@search_id = xml.elements['//searchid'].text end end end def initialize_session Net::HTTP.start(HOSTNAME, PORT) do |http| response = http.get(quot;/k/ident/apisession?token=#{TOKEN}quot;) if body = response.body xml = REXML::Document.new(body) @@session_id = xml.elements['//sid'].text end end end end end

module Kayak Second Cut TOKEN = '12345' HOSTNAME = 'www.kayak.com' PORT = 80 class Flight attr_accessor :session, :search_id, :search_options def method_missing(name, *args) search_options[name] end def initialize(conditions = {}) session ||= Kayak::Session.new self.search_options = {} self.search_options[:origin] = conditions[:from] self.search_options[:destination] = conditions[:to] self.search_options[:depart_date] = conditions[:leaving].strftime('%m/%d/%Y') if conditions[:leaving] self.search_options[:return_date] = conditions[:returning].strftime('%m/%d/%Y') if conditions[:returning] Net::HTTP.start(HOSTNAME, PORT) do |http| response = http.get(quot;/s/apisearch?basicmode=true&oneway=n&destcode=&depart_time=a&return_date=... self.search_id = Kayak.retrieve(response, '//searchid') end end def self.find(args) self.new(args) end end class Session attr_accessor :session_id def initialize Net::HTTP.start(Kayak::HOSTNAME, Kayak::PORT) do |http| response = http.get(quot;/k/ident/apisession?token=#{Kayak::TOKEN}quot;) self.session_id ||= Kayak.retrieve(response, '//sid') end end end def self.find(type, *args) case type when :flights Kayak::Flight.find(*args) end end ...

I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7

Third Cut ?

Always Room for Improvement

Tips

:symbol ignore the colon

I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7

Optional Parentheses looks like a sentence

I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7

Blocks for All In other languages, you have to specify explicitly that a function can accept another function as an argument. But in Ruby, any method can be called with a block as an implicit argument. Matz, 2003

* arrays from anything

Optional Braces not that common

I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7

• Start Modestly • Stay in the Domain • Get Better

56 That’s It travel safely

Ben Scofield ben.scofield@viget.com http://www.extendviget.com/ http://www.culann.com/ 4 November 2007 © Copyright 2007 Viget Labs, LLC – www.viget.com

#puts presentations

Add a comment

Related pages

Ruby Conference 2007 Cleanliness Is Next to Domain ...

Ruby Conference 2007 Cleanliness Is Next to Domain Specificity ... a suggested video will automatically play next ... Domain Specificity ...
Read more

Confreaks TV | Cleanliness Is Next to Domain Specificity ...

Cleanliness Is Next to Domain Specificity. Ben Scofield. This presentation, by Ben Scofield, is licensed under a Creative Commons Attribution ShareAlike 3.0.
Read more

MGT291CC - Domain Specificity - YouTube

... a suggested video will automatically play next. ... Ruby Conference 2007 Cleanliness Is Next to Domain Specificity by Ben Scofield ...
Read more

Scofield Wins Award, Writes Book, Presents in Italy and ...

... "Cleanliness is Next to Domain Specificity," at RubyConf 2007. He will talk about writing code that's specific to a given domain, ...
Read more

I am a Turing Machine

RubyConf 2007: Cleanliness Is Next to Domain Specificity. Ben Scofield is a senior developer at Viget Labs ... Cleanliness Is Next to Domain Speci ...
Read more

RubyConf Recap | Viget

... his "Cleanliness is Next to Domain-Specificity" showed how creating a domain-specific dialect in Ruby can really ... Next; Next Post Software ...
Read more

Learn and talk about Domain specificity, Developmental ...

... all focused on Domain specificity , and makes it easy to learn ... Ruby Conference 2007 Cleanliness Is Next to Domain Specificity by ...
Read more

Blog Archive - notkeepingitreal.com

Blog Archive 2014 Angular with Vim Jun 01 2014 2013 Clojure Conj 2013 ... Cleanliness Is Next to Domain Specificity Nov 04 2007 posted in rubyconf ...
Read more

RubyConf 2007 Presentations Now Online - InfoQ: Software ...

Over the course of the next 4 months, ... InfoQ Homepage News RubyConf 2007 Presentations Now Online. ... Cleanliness Is Next to Domain Specificity;
Read more