Cleanliness is Next to Domain-Specificity

3,111 views
3,015 views

Published on

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

Published in: Technology, Design
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,111
On SlideShare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
39
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Cleanliness is Next to Domain-Specificity

  1. 1. Cleanliness is Next to Domain-Specificity Ben Scofield Senior Developer Viget Labs 4 November 2007 © Copyright 2007 Viget Labs, LLC – www.viget.com
  2. 2. Part 1: Linguistics Part 2: Refactoring
  3. 3. The Ruby Community
  4. 4. http://www.flickr.com/photos/jesper/1395418767/ Interdisciplinary
  5. 5. Linguistics
  6. 6. Categories
  7. 7. Regional Dialects
  8. 8. (more) Regional Dialects
  9. 9. Jargons Cants
  10. 10. Pidgins and Creoles
  11. 11. Vocabulary Grammar
  12. 12. Real DSLs Ruby Domain-Specific Code
  13. 13. ActiveRecord
  14. 14. RSpec
  15. 15. Same Grammar, Different Vocabulary
  16. 16. Who Cares?
  17. 17. DSLs DSL Intimidate and Frighten http://www.flickr.com/photos/cwsteeds/58514985/
  18. 18. Write a Parser? No, Thanks. http://www.flickr.com/photos/rooreynolds/243810988/
  19. 19. http://www.flickr.com/photos/jonosd/498162310/
  20. 20. Change the Vocabulary Change the World Heroes on NBC - Mondays at 9 PM
  21. 21. API vs. Dialect
  22. 22. Why DSanything?
  23. 23. Who Are We?
  24. 24. 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
  25. 25. Linguistic Determinism
  26. 26. The Hopi
  27. 27. Linguistic Relativism
  28. 28. 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 ... ...
  29. 29. Color Perception http://www.flickr.com/photos/thedeplorableword/140856437/
  30. 30. Direction of Causality Degree of Influence
  31. 31. RSpec  Sapir-Whorf
  32. 32. Testing is Too Late
  33. 33. Specifications Come First
  34. 34. RSpec Leads You in the Right Direction
  35. 35. DSDs are Built on Linguistic Relativism
  36. 36. Keep Your Head in the Domain
  37. 37. Refactoring
  38. 38. Tastes Vary
  39. 39. ?
  40. 40. Finding a Ticket
  41. 41. kayak.com
  42. 42. What Does This Do?
  43. 43. Ruby? Awesome!
  44. 44. 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.
  45. 45. Start at the End
  46. 46. I Want to: find flights from CLT to RDU leaving today and returning in one week
  47. 47. I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  48. 48. 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
  49. 49. 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
  50. 50. 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;
  51. 51. 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
  52. 52. 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
  53. 53. 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
  54. 54. 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 ...
  55. 55. I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  56. 56. Third Cut ?
  57. 57. Always Room for Improvement
  58. 58. Tips
  59. 59. :symbol ignore the colon
  60. 60. I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  61. 61. Optional Parentheses looks like a sentence
  62. 62. I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  63. 63. 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
  64. 64. * arrays from anything
  65. 65. Optional Braces not that common
  66. 66. I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  67. 67. • Start Modestly • Stay in the Domain • Get Better
  68. 68. 56 That’s It travel safely
  69. 69. 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

×