<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-9109010163420737861</id><updated>2012-01-14T14:18:44.372-08:00</updated><category term='Python'/><category term='Data Wonk'/><category term='Bedtime Reading'/><category term='Neat'/><category term='AppEngine'/><category term='Philosophy'/><category term='Java'/><category term='When I Have Time'/><category term='Announcement'/><category term='Curmudgeon'/><category term='Protovis'/><category term='Testing'/><category term='Book Reviews'/><category term='Scala'/><category term='Geek Humor'/><category term='iPhone'/><category term='Ruby'/><category term='Projects'/><category term='Objective-C'/><category term='Processing'/><category term='Patterns'/><category term='Spring'/><category term='Miscellaneous'/><category term='Using My Powers For ...'/><category term='Automation'/><category term='Meta'/><title type='text'>An Obsession With Programming</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>67</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-4669777348896715618</id><published>2011-12-19T20:55:00.000-08:00</published><updated>2011-12-19T22:00:21.987-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Book Reviews'/><title type='text'>Books For Systems Geeks</title><content type='html'>I've been involved in lots of system releases over the years. Not only major versions and upgrades for the various companies I've worked for but the monthly or weekly minor (or, "minor") releases that are the norm in startups everywhere.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And like everyone else who's gone through those circuits, I've picked up a bunch of ad hoc, empirically proven knowledge about how to do these things. I know what's worked, and I know what hasn't.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;But as the person helming the online systems for my studio's next major game, I thought it was a good idea to see what other people have to say about the complex art of launching a big system. Not just making it scale but the processes and practices that make it a smooth, worry-free (or as close as one can hope) launch. No sense in avoiding the wisdom of others, after all. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here are my thoughts on a few of the books I've read recently.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;a href="http://www.powells.com/biblio/62-9780137030422-1"&gt;The Art of Scalability&lt;/a&gt;&lt;/i&gt; - This is one of my favorites, and it's a book I'll come back to again and again. While it's light on actual technical details, it does a great job of explaining how scalability decisions are actually business decisions and how you need a culture of scalability, not just a decision to shove it all in at the end.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;That sounds obvious, right? That's the other thing the book does really well: Encapsulating ideas you, like me, have probably learned on your own into nice, articulate concepts. I know about horizontal scaling, sharding, and splitting up servers based on functionality. But when they talk about their X-, Y-, and Z-axis scaling cube, it summarizes it quickly. Little phrases like "technology agnostic design" and "swim lanes" become keywords that you can quickly call up when you're thinking, "Something about this doesn't sound right."&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;a href="http://pragprog.com/book/mnee/release-it"&gt;Release It!&lt;/a&gt;&lt;/i&gt; - I was scribbling down notes constantly while reading this book. This is the distilled advice of someone who's seen lots of systems work well and poorly. It's one of the few tech books that is super relevant even five years or so after it was published. (Though it merely hints at the cloud technology that has sprung up since then.) &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It documents a slew of patterns and antipatterns that will have you nodding your head constantly. A lot of the topics are things that you probably kind of know if you've done a bunch of launches. But this book takes them out of the realm of intuition and into concrete knowledge, real experience, and practical advice.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Its notion that the time before launch is a comparably short time in your product's life versus the time it spends live won't necessarily apply if, like me, you work in the games industry, where years of work go into a product whose user base drops off, under normal circumstances, rather than grows. But that does not diminish the value of the text. (And I won't always be in games.)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;a href="http://scalabilityrules.com/"&gt;Scalability Rules&lt;/a&gt;&lt;/i&gt; - This book is the sequel to &lt;i&gt;The Art of Scalability&lt;/i&gt;, though perhaps companion is a better word. Whereas the authors were light on tech and heavy on business in the first book, this book is all about the technical concepts you need to launch scalable systems. Again, lots of great advice that I scribbled into my notes. (This book has more to say on cloud systems than the original, earlier book did.)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Best of all, they provide a handy list of the 50 rules in order of "amount of risk management you get" and also provide a rough guess at the amount of work. Sort by most bang for the least buck, and you'll be knocking out a few in no time.&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;a href="http://www.powells.com/biblio/61-9780672326998-0"&gt;Scalable Internet Architectures&lt;/a&gt;&lt;/i&gt; - On the other hand, if you &lt;i&gt;really&lt;/i&gt; want technical advice, this book gets way down to the nitty-gritty. There's a lot of good info here, but it's going to be most useful if you're on the IT/operations side of the fence versus the development side. That said, his argument that you should focus on scaling &lt;i&gt;down&lt;/i&gt; (i.e., cutting costs) as well as scaling &lt;i&gt;up&lt;/i&gt; has become a fixture in my architecture.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;a href="http://www.powells.com/biblio/62-9780321601919-1"&gt;Continuous Delivery&lt;/a&gt;&lt;/i&gt; - The notion of continuous delivery, which means that your software is always ready for launch, is a compelling one. The authors are basically trying to convert the seemingly inevitable tension around a production release into a ho-hum experience that most of the team probably doesn't even need to be aware of.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It sounds great, but I'm not sure how much I'll be able to apply to my day-to-day work. The authors set a high bar when they cavalierly suggest that you should have 90 percent coverage just from unit tests. I used to feel fairly good about the fact that I had 40 percent coverage from unit tests and integration tests.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Still, I definitely came out with some ideas that will make our own launches more relaxed once I implement them.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-4669777348896715618?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/4669777348896715618/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2011/12/books-for-systems-geeks.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4669777348896715618'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4669777348896715618'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2011/12/books-for-systems-geeks.html' title='Books For Systems Geeks'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-8075563933025735752</id><published>2011-12-03T16:30:00.000-08:00</published><updated>2011-12-04T11:13:19.809-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Automation'/><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='Using My Powers For ...'/><title type='text'>My Radio Broadcast Podcast</title><content type='html'>While &lt;a href="http://programmingobsession.blogspot.com/2011/07/visualizing-week-of-997.html"&gt;I have an enthusiasm for boppy, bubble-gum music&lt;/a&gt;, particularly as a backdrop to coding, I have also had a passion for opera in the past. I've even had questions used in the Opera Quiz on the &lt;a href="http://www.metoperafamily.org/metopera/broadcast/operainfo.aspx"&gt;Metropolitan Opera radio broadcasts&lt;/a&gt;.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Unfortunately, that passion is hard to fit into my life these days. Opera tickets are expensive unless you want to stand, and performances take a long time. This is the nature of opera.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This used to be easier. On Saturday mornings, I'd turn on the radio broadcast and listen to the opera for several hours as I went about my morning. Then I got into food. And farmers markets, my favorite of which are on Saturday mornings. That then became my normal food shopping day, and the radio broadcast went by the wayside. I'm usually coming home from shopping right as the opera ends.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Sure, you can listen to albums, but the Met broadcasts are fun because they provide context. The host describes the costumes, experts provide backstory, and they do the quiz.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When podcasts became popular, I realized that a podcast of the Met's Saturday matinees would be perfect. I sent letters asking the Met to do it. When they called and asked for money, I'd mention it. I'd even tell them that I would pay for such a podcast. Imagine: paying for Internet content!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;They've never done it. And so I've fallen behind on opera, enjoying it as much as possible with a ticket or two a year to the San Francisco Opera.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Earlier this year, I was ranting about this yet again when I realized that I could probably craft my own podcast based on the radio broadcasts. What I wanted was the ability to call up a podcast on my iPod and see the latest opera broadcast, already synced. So began a day or two, off and on, of work on a podcast-creation script that would use radio stations as its source material. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Rube Goldberg would like this one.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;First, I needed to figure out how to capture the music. A friend suggested &lt;a href="http://fstream.en.softonic.com/mac"&gt;FStream&lt;/a&gt;, Mac OS X software that had two valuable features: I could open a wide range of streaming URLs with it, and it was scriptable. &lt;a href="http://www.amazon.com/Applescript-Disks-Bmugs-Macintosh-Scripting/dp/1568301154"&gt;I like scriptable apps.&lt;/a&gt; And these days, &lt;a href="http://rubyosa.rubyforge.org/"&gt;one can even use Ruby to do that scripting&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;What I ultimately wanted was to not even think about this. That meant that my script would need to know a schedule. It reads in a config file with &lt;a href="http://www.yaml.org/"&gt;YAML&lt;/a&gt; entries that contain the name of the item, the start time, end time, and streaming URL. When the script runs, it parses the file and checks once a minute to see if it should be recording. If it should (and it isn't), it starts up FStream, points it to the appropriate URL, and tells it to start recording. When it reaches the end time, it tells FStream to stop recording. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Once the file is closed, the script uploads it to &lt;a href="http://aws.amazon.com/s3/"&gt;S3&lt;/a&gt; and creates an XML file that points to all the appropriate links of the files. Voila: a podcast of streaming radio.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Though I did this originally with the Metropolitan Opera broadcasts in mind, it obviously works for any streaming radio. I've set up new entries for &lt;a href="http://www.capradio.org/classical/friday-night-at-the-opera"&gt;CapRadio's Friday Night at the Opera&lt;/a&gt;, and &lt;a href="http://www.kdfc.com/The-San-Francisco-Opera-on-KDFC/5805657"&gt;KDFC's San Francisco Opera broadcasts&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There are a couple of problems with this script. One is a bug that causes it to throw an exception when uploading the file. I'll fix that at some point. It just means I have to manually upload the files to S3. The other problem is logistical: Neither of our computers is on all the time. To add one more step to my baroque script, I set myself a reminder so that I know to set up my computer during the appropriate time period. I wonder if iCal is scriptable …&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In addition to all the stuff this script is supposed to do, the script passes its config file through &lt;a href="http://www.ruby-doc.org/stdlib-1.9.3/libdoc/erb/rdoc/ERB.html"&gt;Ruby's ERB system&lt;/a&gt;. That means that I can actually set up my config file so that the start times are programatically driven (e.g., 9:00am on the coming Saturday).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I'd still like the Met to do a podcast of their own. I'd even still pay for it. But until they do, I not only have their broadcasts in a podcast, I have a wealth of others.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here's the script, with various sensitive bits taken out. One thing I've found useful is to put my S3 connectivity information in a separate, included script so that I can distribute the main script and not accidentally include my S3 credentials. On the off chance you want to use this script, you'll need a file named aws_info.rb in the same directory as this script which defines three variables: S3_BUCKET_NAME, S3_ACCESS_KEY, and S3_SECRET_ACCESS_KEY.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;require './awsinfo'&lt;br /&gt;require 'rubygems'&lt;br /&gt;&lt;br /&gt;require 'appscript'&lt;br /&gt;include Appscript&lt;br /&gt;&lt;br /&gt;require 'fileutils'&lt;br /&gt;require 'time'&lt;br /&gt;require 'yaml'&lt;br /&gt;require 'aws/s3'&lt;br /&gt;require 'erb'&lt;br /&gt;require 'rss/1.0'&lt;br /&gt;require 'rss/2.0'&lt;br /&gt;require 'rss/maker'&lt;br /&gt;&lt;br /&gt;class File&lt;br /&gt; def name&lt;br /&gt;     pieces = self.path.split('/')&lt;br /&gt;     pieces[pieces.length - 1]&lt;br /&gt; end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;SLEEP_INTERVAL = 60&lt;br /&gt;PODCAST_FILE = "podcast.xml"&lt;br /&gt;PODCAST_URL = "https://s3.amazonaws.com/#{S3_BUCKET_NAME}"&lt;br /&gt;&lt;br /&gt;$schedule_file = 'opera_schedule.yaml'&lt;br /&gt;$is_recording = false&lt;br /&gt;$current_schedule = nil&lt;br /&gt;&lt;br /&gt;#constants are defined in awsinfo&lt;br /&gt;def start_s3&lt;br /&gt; AWS::S3::Base.establish_connection!(&lt;br /&gt;    :access_key_id     =&amp;gt; S3_ACCESS_KEY,&lt;br /&gt;    :secret_access_key =&amp;gt; S3_SECRET_ACCESS_KEY&lt;br /&gt;  ) &lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def stop_s3&lt;br /&gt; AWS::S3::Base.disconnect!&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def parse_time(time_string)&lt;br /&gt;  regex = /^(\d*?)-(\d*?)-(\d*?)\s*?(\d*?):(\d*)/&lt;br /&gt;  year = time_string[regex,1].to_i&lt;br /&gt;  month = time_string[regex,2].to_i&lt;br /&gt;  day = time_string[regex,3].to_i&lt;br /&gt;  hour = time_string[regex,4].to_i&lt;br /&gt;  minute = time_string[regex,5].to_i&lt;br /&gt;  Time.local(year,month,day,hour,minute)&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def parse_schedule_file(filename = $schedule_file,time=Time.new)&lt;br /&gt;   schedule = File.open(filename,'r') {|file|YAML::load(ERB.new(file.read).result binding)}&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;#side effect: sets $current_schedule if appropriate&lt;br /&gt;def should_be_recording(time=Time.new)&lt;br /&gt;  schedule = parse_schedule_file(filename=$schedule_file,time=time)&lt;br /&gt;  schedule_flag = false&lt;br /&gt;  schedule.each do |schedule_entry|&lt;br /&gt;     start_time = parse_time(schedule_entry['start_time'])&lt;br /&gt;     end_time = parse_time(schedule_entry['end_time'])&lt;br /&gt;     if (start_time..end_time) === time then&lt;br /&gt;         schedule_flag = true&lt;br /&gt;         $current_schedule = schedule_entry&lt;br /&gt;         break&lt;br /&gt;     end&lt;br /&gt;  end&lt;br /&gt;  schedule_flag &lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def next_scheduled_task(time=Time.new)&lt;br /&gt;    schedule = parse_schedule_file(filename=$schedule_file,time=time)&lt;br /&gt;    sorted_schedules = schedule.sort {|a,b| parse_time(a['start_time']) &amp;lt;=&amp;gt; parse_time(b['start_time'])}&lt;br /&gt;    return_schedule = nil&lt;br /&gt;   &lt;br /&gt;    sorted_schedules.each do |entry|&lt;br /&gt;      if parse_time(entry['start_time']) &amp;gt; time then&lt;br /&gt;        return_schedule = entry&lt;br /&gt;        break&lt;br /&gt;      end&lt;br /&gt;   end&lt;br /&gt;   return_schedule&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def add_file_to_podcast(file,schedule_info=$current_schedule)&lt;br /&gt;   xml_file = file_from_pieces(fstreams_dir,PODCAST_FILE)&lt;br /&gt;   rss = nil&lt;br /&gt;   if !File.exists?(xml_file) then&lt;br /&gt;      rss = RSS::Maker.make("2.0") do |maker|&lt;br /&gt;         maker.channel.title = "Derrick's Radio Podcast"&lt;br /&gt;         maker.channel.link = PODCAST_URL + PODCAST_FILE&lt;br /&gt;         maker.channel.description = "Radio programs captured by script"&lt;br /&gt;         maker.items.do_sort = true # sort items by date&lt;br /&gt;      end&lt;br /&gt;      File.open(xml_file,"w") {|file| file.write(rss)}&lt;br /&gt;   end&lt;br /&gt;    &lt;br /&gt;   content = ""&lt;br /&gt;   File.open(xml_file,"r") do |existing_file|&lt;br /&gt;       content = existing_file.read&lt;br /&gt;   end&lt;br /&gt;   rss = RSS::Parser.parse(content,false)&lt;br /&gt;  &lt;br /&gt;   item = item = RSS::Rss::Channel::Item.new&lt;br /&gt;   item.title = schedule_info['name']&lt;br /&gt;   item.date = File.mtime(file)&lt;br /&gt;   item.link = "#{PODCAST_URL}/#{File.new(file).name}"&lt;br /&gt;   item.pubDate = File.mtime(file)&lt;br /&gt;   item.enclosure = RSS::Rss::Channel::Item::Enclosure.new(item.link, File.size(file), 'audio/mpeg')    &lt;br /&gt;   rss.items &amp;lt;&amp;lt; item&lt;br /&gt;  &lt;br /&gt;   File.open(xml_file,"w") {|file| file.write(rss)}&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;#todo: use fstream.recording flag&lt;br /&gt;def is_recording&lt;br /&gt; fstream = app('FStream')&lt;br /&gt; puts fstream.status&lt;br /&gt; $is_recording &amp;amp;&amp;amp; fstream.status == 3&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def fstreams_dir&lt;br /&gt;  fstream_path = './fstreams'&lt;br /&gt;  FileUtils.mkdir_p(fstream_path)&lt;br /&gt;  Dir.new(fstream_path)&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def file_from_pieces(dir,file)&lt;br /&gt;  "#{dir.path}/#{file}"&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def sync_dir&lt;br /&gt; dir = fstreams_dir&lt;br /&gt; start_s3&lt;br /&gt; dir.entries.each do |filename|&lt;br /&gt;    next if filename =~ /^\..*/&lt;br /&gt;    file_path = file_from_pieces(dir,filename)&lt;br /&gt;   &lt;br /&gt;    # if the file doesn't exist (or it's the podcast file), upload it&lt;br /&gt;    if !AWS::S3::S3Object.exists?(S3_BUCKET_NAME,filename) || filename == PODCAST_FILE then&lt;br /&gt;        AWS::S3::S3Object.store(filename,open(file_path),S3_BUCKET_NAME)&lt;br /&gt;    end&lt;br /&gt; end&lt;br /&gt; stop_s3&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def s3_safe_name(english_name)&lt;br /&gt;  english_name.gsub(/\s/,'_').downcase&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def start_recording&lt;br /&gt; $is_recording = true&lt;br /&gt; puts 'Starting to record'&lt;br /&gt; fstream = app('FStream')&lt;br /&gt; fstream.openStreamWithURL($current_schedule['from_url'])&lt;br /&gt; fstream.startRecording&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def stop_recording&lt;br /&gt; puts 'Stopping recording'&lt;br /&gt; fstream = app('FStream')&lt;br /&gt; fstream.stopRecording&lt;br /&gt; fstream.stopPlaying&lt;br /&gt;&lt;br /&gt; # find the file most recently created, and rename it&lt;br /&gt; dir = fstreams_dir&lt;br /&gt; filepaths = []&lt;br /&gt; dir.entries.each do |filename|&lt;br /&gt;    next if filename =~ /^\..*/&lt;br /&gt;    file paths &amp;lt;&amp;lt; file_from_pieces(dir,filename)&lt;br /&gt;  end&lt;br /&gt;  filepaths.sort {|a,b| File.mtime(b) &amp;lt;=&amp;gt; File.mtime(a)}&lt;br /&gt;  filepath = file_from_pieces(dir,s3_safe_name($current_schedule['name'])+".mp3")&lt;br /&gt;&lt;br /&gt;  FileUtils.mv(filepaths[0],filepath)&lt;br /&gt;  &lt;br /&gt;  # file can take a while to close&lt;br /&gt;  while true&lt;br /&gt;     begin&lt;br /&gt;       sleep(10)&lt;br /&gt;       File.mtime(filepath)&lt;br /&gt;       break&lt;br /&gt;     rescue exception&lt;br /&gt;       puts "Waiting for #{filepath} to close"&lt;br /&gt;     end   &lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # update podcast file and S3&lt;br /&gt;  add_file_to_podcast(filepath)&lt;br /&gt;  sync_dir&lt;br /&gt;  &lt;br /&gt;  $is_recording = false&lt;br /&gt;  $current_schedule = nil&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def do_start_app&lt;br /&gt;  while(true)&lt;br /&gt;    if !is_recording &amp;amp;&amp;amp; should_be_recording then&lt;br /&gt;      start_recording&lt;br /&gt;    elsif is_recording &amp;amp;&amp;amp; !should_be_recording then&lt;br /&gt;      stop_recording&lt;br /&gt;    else&lt;br /&gt;      next_sched = next_scheduled_task&lt;br /&gt;      puts "#{Time.new} Next scheduled task is #{next_sched['name']} starting at #{next_sched['start_time']}" if next_sched&lt;br /&gt;    end&lt;br /&gt;    sleep(SLEEP_INTERVAL)&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;$schedule_file = ARGV[0] if ARGV.length &amp;gt;= 1&lt;br /&gt;do_start_app if !$testing_mode&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-8075563933025735752?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/8075563933025735752/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2011/12/my-radio-broadcast-podcast.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/8075563933025735752'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/8075563933025735752'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2011/12/my-radio-broadcast-podcast.html' title='My Radio Broadcast Podcast'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-294121587124328955</id><published>2011-11-03T19:00:00.000-07:00</published><updated>2011-11-04T07:33:49.518-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Objective-C'/><title type='text'>Partitioned Concurrency</title><content type='html'>Here is a common block of code in Java. Assume &lt;code&gt;cache&lt;/code&gt; is a &lt;code&gt;Map&lt;/code&gt; of some form.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;if (!cache.containsKey(someKey)) {&lt;br /&gt;  cache.put(someKey,someValue);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Or: if a given object is not in the map with a certain key, put it there. Otherwise, proceed along your merry way.&lt;br /&gt;&lt;br /&gt;I was writing code like this in a controller the other day, because I wanted to cache &lt;code&gt;Broadcaster&lt;/code&gt; objects (from the &lt;a href="http://atmosphere.java.net/"&gt;Atmosphere&lt;/a&gt; framework) that would manage separate chat channels. Requests would come in and get attached to one or another &lt;code&gt;Broadcaster&lt;/code&gt; based on a channel ID.&lt;br /&gt;&lt;br /&gt;But that code snippet, in its current form, isn't thread-safe. You could have two threads get past the if and thus both think that the map needs the object inserted. That isn't always a problem &amp;mdash; my reflection-based API marshaling layer caches &lt;code&gt;Field&lt;/code&gt; and &lt;code&gt;Method&lt;/code&gt; objects that are constant for a given class, making me unconcerned about threads that replace values &amp;mdash; but in this case, you could have the second thread replace the &lt;code&gt;Broadcaster&lt;/code&gt; that the first one inserted, meaning that some chunk of the chat clients wouldn't see messages because they'd be attached to the wrong object.&lt;br /&gt;&lt;br /&gt;The standard way to solve this is to put a mutex around the cache-modification code:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;synchronized(this) {&lt;br /&gt;  if (!cache.containsKey(someKey)) {&lt;br /&gt;      cache.put(someKey,someValue);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;But this is code that is likely to have a lot of requests fired at it, and synchronizing this way introduces a major performance bottleneck. Every single request would have to wait for that lock. Yes, I know: &lt;a href="http://en.wikipedia.org/wiki/Program_optimization#When_to_optimize"&gt;Premature optimization is the root of all evil.&lt;/a&gt; Still, it seemed problematic. Synchronization at that high of a level can cause major issues when you're dealing with hundreds of thousands of concurrent users, which we may very well be when we launch.&lt;br /&gt;&lt;br /&gt;As I was pondering this, I remembered a technique for increasing concurrency. I had discussed it with friends before, but never implemented it. Still, it's straightforward enough. (In fact, it's how &lt;code&gt;java.util.concurrent.ConcurrentHashMap&lt;/code&gt; is implemented, or near enough.) Note that this technique should also work in Objective-C and any other language with similar semantics, though I haven't tried it anywhere other than Java.&lt;br /&gt;&lt;br /&gt;Here's the idea: If you've got some sort of cache, you only really need to make sure that two threads aren't working on the same cache entry at the same time. If I have a &lt;code&gt;Broadcaster&lt;/code&gt;, and I attach it to some ID for the channel, I only care about isolating activity around that ID. I don't care if someone's working on some other ID. (except when I do: see below) In other words, if I'm working with an ID of 3, I don't care if some other thread is checking about whether or not ID 1 already exists; I only care that someone else asking about ID 3 doesn't cause problems.&lt;br /&gt;&lt;br /&gt;So if you could create a sequence of locks, and just ensure that anyone working on the same ID ends up synchronizing on the same lock, you're good to go.&lt;br /&gt;&lt;br /&gt;Consider this code:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;public class ConcurrentCache {&lt;br /&gt;  private static Object[] locks = new Object[10];&lt;br /&gt;  static {&lt;br /&gt;       for (int lockIndex = 0; lockIndex &amp;lt; locks.length; lockIndex++) {&lt;br /&gt;               locks[lockIndex] = new Object();&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private Map cache = new HashMap();&lt;br /&gt;    public Object getOrInsertIntoCache(Object key) { &lt;br /&gt;           synchronized(locks[key.hashCode() % locks.length]) { &lt;br /&gt;                 if (!cache.containsKey(key)) {&lt;br /&gt;                      cache.put(someKey,new Object());&lt;br /&gt;                 } &lt;br /&gt;               return cache.get(key);&lt;br /&gt;            }&lt;br /&gt;     }&lt;br /&gt; } &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The static initialization code gives you ten locks to work with, and you can get to one by just modding the hash code by the length of the list. Any given ID will always end up with the same lock (assuming a consistent &lt;code&gt;hashCode&lt;/code&gt; result, which is true of &lt;code&gt;Long&lt;/code&gt;, &lt;code&gt;Integer&lt;/code&gt;, &lt;code&gt;String&lt;/code&gt;, and other object representations of Java primitives typically used as keys.&lt;br /&gt;&lt;br /&gt;To test this, I wrote a simple program that would put 5,000 tasks on an &lt;code&gt;ExecutorService&lt;/code&gt; with ten threads. Each thread would generate a random number between one and 100. That would become the key to use on the cache. Depending on some command-line arguments, those threads would either lock on the cache or on a partitioned lock as above. Any given thread captured current system time when it was constructed (put on the queue) and then printed the difference between the new system time and the start time when it eventually ran. I ran the program and eagerly checked the average wait times each thread experienced.&lt;br /&gt;&lt;br /&gt;Only to discover that they had almost identical performance. The partitioned code showed threads waiting a mere millisecond less, on average, than the "block on everything" code. And that was averaged over 5,000 jobs, remember. That's a lot of complexity for not a lot of gain.&lt;br /&gt;&lt;br /&gt;I figured out what was going on with a bit of poking. The basic code says: See if the key's already there, and, if it's not, insert it. What that really meant was that 4,900 of my jobs called &lt;code&gt;containsKey&lt;/code&gt;, saw that the key was there, and exited. I had an extraordinarily high cache hit rate. The lock was acquired and released so quickly that there simply wasn't noticeable lock contention.&lt;br /&gt;&lt;br /&gt;Once I realized that, I made a simple change. After doing the cache logic, I simply had each thread sleep for one millisecond. And I ran my program again.&lt;br /&gt;&lt;br /&gt;That produced the results I expected! Threads in the "block on everything" version waited almost precisely ten times as long, on average, as their partitioned cousins (29 seconds versus 3).&lt;br /&gt;&lt;br /&gt;Real-world caches are messy things. In fact, my simple test case wouldn't really be done this way at all: You'd prefill the cache with the fixed items you wanted and avoid all this nonsense. But real caches need to expire items, cached results can be more or less complex, and so forth. The caching situation I have involves lots of different IDs with corresponding amounts of object churn. So the &lt;em&gt;in situ&lt;/em&gt; results will likely be very different than an almost exact division. Still, it obviously made a big difference for time-consuming activity and at least didn't hurt performance in the simple case. You have more code complexity, which means more potential bugs and less maintainability, but in my system this logic is tucked into a class by itself, so no clients need to worry about these details: They just request a &lt;code&gt;Broadcaster&lt;/code&gt; and don't worry about how it gets to them. &lt;br /&gt;&lt;br /&gt;Still, if your cache handling is no more sophisticated than this, synchronizing on the whole thing was basically equal in speed, and you should therefore avoid the readability/maintainability cost altogether. If you're worrying about a one-millisecond difference across 5,000 tasks in your server code, you're farther along in your optimizations than I am.&lt;br /&gt;&lt;br /&gt;There's also the question of what to do when you do care about the overall state of the cache. For instance, what if you want to get the size of this cache? The above technique won't work, because no one lock guarantees a fixed state. (Ignoring the reality that you probably don't actually care about the &lt;em&gt;exact&lt;/em&gt; size of the cache: You simply want the approximation, in which case you're fine.)&lt;br /&gt;&lt;br /&gt;Actually, there is one lock that will guarantee the state of the overall cache, and that's the cache itself. If you really care about exact counts, you can synchronize on the cache itself for anything that changes its size. For instance:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;public class ConcurrentCache {&lt;br /&gt;  private static Object[] locks = new Object[10];&lt;br /&gt;  static {&lt;br /&gt;       for (int lockIndex = 0; lockIndex &amp;lt; locks.length; lockIndex++) {&lt;br /&gt;               locks[lockIndex] = new Object();&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private Map cache = new HashMap();&lt;br /&gt;    public Object getOrInsertIntoCache(Object key) { &lt;br /&gt;           synchronized(locks[key.hashCode() % locks.length]) { &lt;br /&gt;                 if (!cache.containsKey(key)) {&lt;br /&gt;                      synchronized(cache) {&lt;br /&gt;                          cache.put(someKey,new Object());&lt;br /&gt;                      }&lt;br /&gt;                 } &lt;br /&gt;               return cache.get(key);&lt;br /&gt;            }&lt;br /&gt;     }&lt;br /&gt; } &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;You re-introduce the global lock, but you minimize when it's acquired. If you have a high cache hit rate, this should still give you better concurrency but allowing for across-the-board thread safety (though, again, do you &lt;em&gt;really&lt;/em&gt; care that your size is 99 and not 98?)&lt;br /&gt;&lt;br /&gt;This isn't a new technique, but it was worth jotting down so I don't forget it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-294121587124328955?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/294121587124328955/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2011/11/partitioned-concurrency.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/294121587124328955'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/294121587124328955'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2011/11/partitioned-concurrency.html' title='Partitioned Concurrency'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-4096988757618764539</id><published>2011-10-15T14:27:00.001-07:00</published><updated>2011-10-16T10:27:21.360-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Neat'/><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><title type='text'>Faking Block Programming In Java</title><content type='html'>While I was writing some server-side code to talk to a web service, I did the total n00b thing of forgetting to close my connection when I was done with the call. This works fine in test cases, but in any sort of real-world situation, you'll quickly exhaust your connection pool as connections linger, unavailable, until they time out. And as I fixed the problem, I realized I had done the same thing in one other place &amp;mdash; I haven't written client code in a while. Then I realized that I had a recurring pattern:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;HttpMethod get;&lt;br /&gt;try {&lt;br /&gt;    get = new GetMethod(url);&lt;br /&gt;    get.execute();&lt;br /&gt;    &lt;br /&gt;    // pull the response body out and do stuff with it&lt;br /&gt;} finally {&lt;br /&gt;   if (get != null) {&lt;br /&gt;       get.releaseConnection();&lt;br /&gt;   }&lt;br /&gt;}&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This ensures that, even in the case of an Exception, the connection gets released. Fairly straightforward. But who wanted little copies of that code all over the system? And, worse, what if someone forgot to do this, just as I did? Anytime you set up code people have to remember to type, you ensure that someday someone will forget.&lt;br /&gt;&lt;br /&gt;This would be an obvious use case for a &lt;a href="http://en.wikipedia.org/wiki/Closure_(computer_science)"&gt;closure&lt;/a&gt;. In fact, it reminded me of the &lt;code&gt;File.open&lt;/code&gt; method in Ruby that takes a block of code as an argument. The method creates a &lt;code&gt;File&lt;/code&gt; object, calls the block of code you pass in with said &lt;code&gt;File&lt;/code&gt; object, and then closes the file even if there was an exception.&lt;br /&gt;&lt;br /&gt;The only problem: pure Java doesn't support closures. (Some JVM-compatible languages like Scala do, however.)  &lt;br /&gt;&lt;br /&gt;But you can mimic the behavior to some degree with anonymous inner classes, and you can use Java Generics to provide type checking. I created a &lt;code&gt;MethodOperator&lt;/code&gt; interface that looks like this&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;public interface MethodOperator&amp;lt;T&amp;gt; {&lt;br /&gt;    public T actOnMethodPostResponse(HttpMethod method) throws Exception;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;public T &amp;hellip;&lt;/code&gt; bits basically mean that when I have to instantiate one of these, I can also declare it as being of some type, which then gets returned from the one method.&lt;br /&gt;&lt;br /&gt;Once I had that code, I added a simple utility method:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;    protected &amp;lt;T&amp;gt; T actOnHttpMethod(HttpMethod method, MethodOperator operator) throws Exception {&lt;br /&gt;        try {&lt;br /&gt;            executeMethod(method); // utility method that checks for errors and so forth on the method&lt;br /&gt;            return (T)operator.actOnMethodPostResponse(method);&lt;br /&gt;        } catch (Exception e) {&lt;br /&gt;            log.error("Error talking to http server",e);&lt;br /&gt;            throw e;&lt;br /&gt;        } finally {&lt;br /&gt;            if (method != null) {&lt;br /&gt;                method.releaseConnection();&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And can invoke it with something like this:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt; IdResponse response = actOnHttpMethod(post,new MethodOperator&amp;lt;IdResponse&amp;gt;() {&lt;br /&gt;    public IdResponse actOnMethodPostResponse(HttpMethod method) {&lt;br /&gt;        // unmarshal the response and create an IdResponse object with it&lt;br /&gt;     }&lt;br /&gt; });&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;actOnHttpMethod&lt;/code&gt; will do the request, hand my object the response, and then close the connection for me.&lt;br /&gt;&lt;br /&gt;Inner classes definitely suffer from readability problems, but this setup ensures that it's very easy for developers to not even think about connection management. Furthermore, I can add features and have them automatically used by every client. For instance, if I wanted to profile the request/response time or add logging. If I ever want to add support for asynchronous calls, I can write a new utility method that does all the work of enqueuing the method and so forth, invoking the &lt;code&gt;MethodOperator&lt;/code&gt; code as needed, and then change specific code to say &lt;code&gt;actOnHttpMethodAsync&lt;/code&gt; or something instead of &lt;code&gt;actOnHttpMethod&lt;/code&gt;. A minimal change in client code plus a utility method, and I've added a more scalable alternative in situations where I don't care about waiting for the response.&lt;br /&gt;&lt;br /&gt;Once I refactored all that away, I then realized that I could refactor even more. At the moment, I handle a response in one of two ways: I either ignore it (for things like DELETEs) or I unmarshal the contents from XML into Java. Once I had my whole framework in place, I realized I could make implementations of the &lt;code&gt;MethodOperator&lt;/code&gt; interface that would cover these two cases.&lt;br /&gt;&lt;br /&gt;I created the following:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;public class IgnoreResponseMethodOperator implements MethodOperator&amp;lt;Object&amp;gt; {&lt;br /&gt;    public Object actOnMethodPostResponse(HttpMethod method) {&lt;br /&gt;        return null;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public class XmlUnmarshallingMethodOperator&amp;lt;T&amp;gt; implements MethodOperator {&lt;br /&gt;    public T actOnMethodPostResponse(HttpMethod method) throws Exception {&lt;br /&gt;        JAXBContext context = JAXBContext.newInstance(Constants.JAXB_PACKAGES);&lt;br /&gt;        Unmarshaller unmarshaller = context.createUnmarshaller();&lt;br /&gt;        return (T)unmarshaller.unmarshal(method.getResponseBodyAsStream());&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Now my client code actually looks like this:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;       IdResponse response = actOnHttpMethod(post,new XmlUnmarshallingMethodOperator&amp;lt;IdResponse&amp;gt;());&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;I still get all the value of the code that manages connections around my code, but now I don't even have to worry about the unreadability of anonymous inner classes.&lt;br /&gt;&lt;br /&gt;(You could make the case that this will create a lot of object churn. If it does, I can look at making a thread-safe implementation that will let me re-use the &lt;code&gt;MethodOperator&lt;/code&gt; objects. That's very easy to do with the &lt;code&gt;IgnoreResponseMethodOperator&lt;/code&gt;, but tougher with the type-safe xml unmarshaller. I imagine I'd have to create instances for each type of object I might get back. Given that there aren't too many, this wouldn't be too bad. But first I'll see if that's actually a problem.)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-4096988757618764539?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/4096988757618764539/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2011/10/faking-block-programming-in-java.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4096988757618764539'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4096988757618764539'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2011/10/faking-block-programming-in-java.html' title='Faking Block Programming In Java'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-6371715240123050024</id><published>2011-09-24T20:05:00.000-07:00</published><updated>2011-09-25T08:36:03.870-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Protovis'/><category scheme='http://www.blogger.com/atom/ns#' term='Using My Powers For ...'/><category scheme='http://www.blogger.com/atom/ns#' term='Data Wonk'/><title type='text'>Protovis And Wine Visualization: California Crush Statistics</title><content type='html'>&lt;a href="http://programmingobsession.blogspot.com/2011/09/radio-station-playlist-data.html"&gt;Radio station visualizations&lt;/a&gt; are fun &lt;a href="http://programmingobsession.blogspot.com/2011/07/visualizing-week-of-997.html"&gt;and all&lt;/a&gt;, but I realized that I should research data visualization by looking at data I actually care about. That way, I can provide context and ask deeper questions about the subject matter at hand.&lt;br /&gt;&lt;br /&gt;As an &lt;a href="http://obsessionwithfood.com/"&gt;occasional wine writer&lt;/a&gt;, data about the wine industry seemed like a good start.&lt;br /&gt;&lt;br /&gt;Harvest — "crush" in wine industry jargon — is afoot here in California, and that spurred me to search for data on previous harvests. The &lt;a href="http://www.nass.usda.gov/"&gt;National Agricultural Statistics Service&lt;/a&gt; publishes a range of interesting data for wine geeks, some of which I've been using for experiments and explorations with &lt;a href="http://mbostock.github.com/protovis/"&gt;Protovis&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The first public one shows harvest statistics over 20 years for the 15 grapes with highest crush numbers in California in 2010.  The &lt;a href="http://s3.amazonaws.com/dfsvisualizations/grape_tonnage.html"&gt;interactive version&lt;/a&gt; gives you a deeper view, with detailed per-year statistics as you mouse over, but here's a static version to give you an idea.&lt;br /&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;a href="http://s3.amazonaws.com/dfsvisualizations/grape_tonnage.html"&gt;&lt;img src="http://4.bp.blogspot.com/-_h97Z1EIGlE/Tn9Bcf9lgfI/AAAAAAAAAF4/W2gi8fnDmeE/s400/Screen%2BShot%2B2011-09-25%2Bat%2B7.56.54%2BAM.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5656311614872584690" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 302px; " /&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;div style="text-align: center;"&gt;&lt;span class="Apple-style-span"  style="color:#0000ee;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;Groovy, eh?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Wine geeks will know many of this visualization's stories well. The California wine industry has grown tremendously over the last 20 years, thanks to increased consumption in the United States. Grape gluts are periodic, but 2005 was a &lt;a href="http://www.bohemian.com/bohemian/03.29.06/wine-0613.html"&gt;particularly grape-heavy year&lt;/a&gt;. Industrial grapes such as French Colombard, Rubired, and Ruby Cabernet are mainstays of the bulk-wine industry led by Gallo. Pinot Noir tonnage surpassed Syrah tonnage in 2008, about 4 years — when vines start producing worthwhile fruit — after Sideways, the movie that told everyone about Pinot Noir. (Though I should note that I prefer the Pinots of Oregon and the Sonoma Coast to those of Santa Barbara, the setting for the movie. But, really, I prefer the Pinots of Burgundy to those from anywhere else.)&lt;br /&gt;&lt;br /&gt;But some items in the data surprised me. Merlot, a common Bordeaux variety, went from almost nothing in 1991 to a dominant grape in 2010. Grenache, the popular, fruity darling of the &lt;a href="http://www.rhonerangers.org/"&gt;Rhone Rangers&lt;/a&gt;, has actually seen lower crush values in the last 20 years. Pinot Gris has gone from a nonexistent grape in California to one of the top 15 in the state in just over a decade. Tonnage of French Colombard has gone down, which makes me wonder how the industrial market is doing overall.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt; &lt;/span&gt;&lt;br /&gt;But if you're reading this blog, you're probably more interested in the technical aspects of this data. I used Protovis, and I have repeatedly found that getting a basic visualization up and running with the library is very fast. Getting the fine details right, however, is much slower. It takes a lot of trial and error to get the language to do what you want. I might switch to D3, its successor, for my next projects. It supposedly gives finer control over your visualization.&lt;br /&gt;&lt;br /&gt;What I also keep realizing is that visualizing some set of data isn't really an issue. Organizing the data is. I know this isn't news to anyone who works with data, but these projects are good reminders of how much work that can be.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I started with 20 separate spreadsheets from the NASS and wrote a Ruby script to extract out the bits of data I wanted and compile them into a JSON object I could serve to this chart's HTML page. But even then, the page's JavaScript has to do some processing as well to get the data in a format that Protovis can easily work with. The &lt;a href="http://documentcloud.github.com/underscore/"&gt;Underscore JavaScript library&lt;/a&gt; is a handy tool for doing data transformations.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;But I also used that preprocessing to cache certain items such as the pretty-printed numbers, the colors to use for the different areas (which I calculated with the excellent &lt;a href="http://0to255.com/"&gt;0to255.com&lt;/a&gt;) and other useful items.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-6371715240123050024?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/6371715240123050024/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2011/09/protovis-and-wine-visualization.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6371715240123050024'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6371715240123050024'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2011/09/protovis-and-wine-visualization.html' title='Protovis And Wine Visualization: California Crush Statistics'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-_h97Z1EIGlE/Tn9Bcf9lgfI/AAAAAAAAAF4/W2gi8fnDmeE/s72-c/Screen%2BShot%2B2011-09-25%2Bat%2B7.56.54%2BAM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-9214396116270529270</id><published>2011-09-03T17:01:00.001-07:00</published><updated>2011-09-04T09:28:25.752-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Processing'/><category scheme='http://www.blogger.com/atom/ns#' term='Projects'/><category scheme='http://www.blogger.com/atom/ns#' term='Using My Powers For ...'/><category scheme='http://www.blogger.com/atom/ns#' term='Data Wonk'/><title type='text'>Radio Station Playlist Data Visualization, Part 2</title><content type='html'>&lt;a href="http://2.bp.blogspot.com/-hdPU0iEmKr4/TmOmQhce_hI/AAAAAAAAAFs/b2jt1zMwC4g/s1600/Screen%2BShot%2B2011-09-04%2Bat%2B9.23.34%2BAM.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;/a&gt;As soon as I did my &lt;a href="http://programmingobsession.blogspot.com/2011/07/visualizing-week-of-997.html"&gt;visualization of 99.7's music selection for a week&lt;/a&gt;, I asked the obvious next question: How does &lt;a href="http://997now.radio.com/"&gt;99.7&lt;/a&gt; compare to other "adult contemporary" radio stations?&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There's &lt;a href="http://s3.amazonaws.com/dfsradioplaylists/viz.html"&gt;an interactive version&lt;/a&gt; that lets you drill down into the graph, but here's a screenshot.&lt;/div&gt;&lt;div&gt;&lt;a href="http://s3.amazonaws.com/dfsradioplaylists/viz.html" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://2.bp.blogspot.com/-hdPU0iEmKr4/TmOmQhce_hI/AAAAAAAAAFs/b2jt1zMwC4g/s400/Screen%2BShot%2B2011-09-04%2Bat%2B9.23.34%2BAM.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5648541160438693394" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 364px; " /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;People listen to radio stations for all sorts of reasons, of course, so I don't know that anyone actually cares about this. But it did give me a chance to look at &lt;a href="http://mbostock.github.com/protovis/"&gt;Protovis&lt;/a&gt; and compare it to &lt;a href="http://processing.org/"&gt;Processing&lt;/a&gt; as I learn about data visualization toolkits.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:130%;"&gt;Gathering Data&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;When I gathered data for my first visualization, I wrote a simple script that grabbed songs from the 99.7 website. I set that up as a cron job on an EC2 instance and let it go.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;I did the same thing for the other four radio stations I decided to look at. &lt;a href="http://radioalice.radio.com/"&gt;97.3&lt;/a&gt; uses the same website tech as 99.7, and &lt;a href="http://www.kfog.com/"&gt;KFOG&lt;/a&gt; and &lt;a href="http://www.kbay.com/"&gt;KBAY&lt;/a&gt; share a different website tech, so those got me two stations for the price of one. &lt;a href="http://www.1013.com/main.html"&gt;101.3&lt;/a&gt; uses yet another system. Once I had my scripts running, I just had to wait until I had the same week's worth of data from all stations. A bit of cleanup on the data, a quick change to JSON from comma-separated values, and I was ready to go.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;I decided to use the concept of &lt;a href="http://en.wikipedia.org/wiki/Small_multiple"&gt;small multiples&lt;/a&gt; to provide a quick comparison between stations, but then showing an enlarged version for deeper exploration. Each small graph in the chart represents one station across the same span of time.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:130%;"&gt;Protovis Vs. Processing&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;It took me some time to learn Protovis. I feel that only now, after finishing one visualization, do I really have a grasp on how it works. It seeks to be a declarative language, which means that you define the result and let the under-the-hood bits figure out how to get you there, but I found myself struggling against the lack of control.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;Processing gives you that control. You have vast amounts of control, but that's because it starts you with a blank slate. You can probably do anything you want, but the flip side is that you have to do everything you want.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;But Processing comes with a strong disadvantage: It creates Java applets. Remember those? I barely do, and I was actually writing Java when that's all people did with it. An applet takes a long time to load in a world where website visitors are accustomed to instant gratification from your page. An applet also won't work on your iOS device. So my first visualization was completely unusable by iPad owners.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;(Yes, there is &lt;a href="http://processingjs.org/"&gt;Processing.js&lt;/a&gt;, but my attempts to use it only frustrated me. It didn't support Java generics, and even when I removed them from my code, it failed with cryptic errors that were impossible to debug.)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;As with so many things, deciding on a visualization toolkit means figuring out what's best for your job. If you're doing something complex and custom, you'll probably want Processing. But for a lot of web-based visualizations, I think Protovis will give you what you need once you figure out how to use it. It can certainly &lt;a href="http://www.google.com/search?q=protovis+examples&amp;amp;hl=en&amp;amp;client=safari&amp;amp;rls=en&amp;amp;prmd=ivns&amp;amp;source=lnms&amp;amp;tbm=isch&amp;amp;ei=nMdiTpfIB6zRiAKq1o25Cg&amp;amp;sa=X&amp;amp;oi=mode_link&amp;amp;ct=mode&amp;amp;cd=2&amp;amp;ved=0CAwQ_AUoAQ&amp;amp;biw=1436&amp;amp;bih=725#hl=en&amp;amp;client=safari&amp;amp;rls=en&amp;amp;tbm=isch&amp;amp;sa=1&amp;amp;q=protovis&amp;amp;pbx=1&amp;amp;oq=protovis&amp;amp;aq=f&amp;amp;aqi=g2&amp;amp;aql=&amp;amp;gs_sm=e&amp;amp;gs_upl=5654l5654l0l6744l1l1l0l0l0l0l176l176l0.1l1l0&amp;amp;bav=on.2,or.r_gc.r_pw.r_cp.&amp;amp;fp=9b6da329ae1d3e0b&amp;amp;biw=1436&amp;amp;bih=725"&gt;do a lot in that space&lt;/a&gt;. &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:100%;"&gt;I have still more visualizations in mind for this same set of data, and I'm planning on starting with Protovis (or its successor, &lt;a href="http://mbostock.github.com/d3/"&gt;d3&lt;/a&gt;). The Java applet problems are too big.&lt;/span&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-9214396116270529270?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/9214396116270529270/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2011/09/radio-station-playlist-data.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/9214396116270529270'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/9214396116270529270'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2011/09/radio-station-playlist-data.html' title='Radio Station Playlist Data Visualization, Part 2'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-hdPU0iEmKr4/TmOmQhce_hI/AAAAAAAAAFs/b2jt1zMwC4g/s72-c/Screen%2BShot%2B2011-09-04%2Bat%2B9.23.34%2BAM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-9121880152018503126</id><published>2011-08-24T09:15:00.000-07:00</published><updated>2011-08-24T22:37:57.067-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Meta'/><title type='text'>Hiring Online Engineers</title><content type='html'>(By the way, &lt;a href="https://jobs.ea.com/search/quick.do?q=emeryville"&gt;Maxis is hiring&lt;/a&gt;).&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I've interviewed a lot of candidates over the years. At my last job, we seemed to have an unending stream of candidates because we had learned to be picky. And I didn't even do the phone screens that kept the numbers down. But I did do what we affectionately called the "make them cry" part of the interview, drilling down on Java knowledge as much as I could.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;At Maxis, we have less candidate churn, but I now have to sift through the resumés and do the phone screens. And I'd like to pass along some advice about getting your resumé to the top of my pile. None of these are absolutes, but the more flaws in your resumé, the greater your strengths need to be.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;First, have someone proofread your resumé. I don't care if English is your first language or not; get a good editor to give you feedback. I'll wince at one typo but let it slide. Much more than that, and I begin to wonder if your code will be as unprofessional as your text. A recent candidate said in the header of his resumé that he's a "self-mortification" person. I'm guessing he meant self-motivated, but clearly not self-motivated enough to have someone sanity check his text.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In a similar vein, make sure you use the correct terms when discussing things you know. You do not "program in" AJAX, HTML, XML, or CSS. You may understand them, but it doesn't look like you do if you call them programming languages.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Next, keep your resumé brief. I've always liked the saying, "the only thing on the second page of a resume should be the Nobel Prize you won," but I am in the minority. The style du jour is to make your resume as long as possible. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I'll grudgingly accept a two-page resumé even without the Nobel Prize, but a recent applicant I saw had a many-page resumé with this line item: "Coded Perl functions, invoking subroutines and functions calling functions." In other words, you did some programming? And that was simply the most ridiculous in a long list. "Set up Object-Relational Mapping with Hibernate" is another common line item. For those who don't know, this involves writing a config file. If you have items such as these on your resumé, you're padding, and I'll think you need to pad because you don't have any real skills.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you're a mid-level Java engineer with some enterprise software experience, I'm afraid your resumé looks like approximately 100,000 others. Spring, Hibernate, JUnit, Struts, MySQL, Oracle. I'm yawning already. So make your projects sound interesting. We don't all get to work on famous video games, but before I did, I worked on projects you've never heard of. And I made them sound neat. I focused on the compelling problems and described those. If you can't find interesting problems in your work, you're not a programmer I want to work with: Every problem is interesting in its own way.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I also look for personal projects on a resumé. Yes, people have families and whatnot. But think of great writers. They don't just write because they are required to: They write because they need to. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you're working on personal projects, no matter how esoteric, you're telling me that you're so in love with programming that you can't simply kick it aside when you clock out. You're telling me you love the craft, the problem-solving, the tinkering. You're telling me that you're a programmer I want to work with.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-9121880152018503126?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/9121880152018503126/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2011/08/hiring-online-engineers.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/9121880152018503126'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/9121880152018503126'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2011/08/hiring-online-engineers.html' title='Hiring Online Engineers'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-7585827251581858644</id><published>2011-08-07T08:44:00.000-07:00</published><updated>2011-08-07T09:33:57.558-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='Data Wonk'/><title type='text'>Visualizing My Bike Rides</title><content type='html'>&lt;div&gt;My three main interests are programming, writing, and food and wine.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You'll notice exercise isn't on that list. In fact, quite the opposite: My primary interests are all anti-exercise. But I grudgingly acknowledge its value, and I've decided to "trick" myself into exercising more by turning it into a programming project.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I live close enough to work that I can commute on my bike, and so I've started a project to gather data on my rides and do interesting things with that data, inspired in part by Cooper Smith's &lt;a href="http://cargocollective.com/coopersmith#1327371/Nike-Plus-Visualization"&gt;visualization of Nike+ data from New York City&lt;/a&gt;. Since I need a lot of data to make useful visualizations, I'm riding more consistently to get it. And hopefully by the time I have enough data, the bike riding will seem routine.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I picked up a couple of bike tracking apps for the iPhone, but have settled on &lt;a href="http://www.abvio.com/cyclemeter/"&gt;Abvio's Cyclemeter&lt;/a&gt;, based on the recommendation of a co-worker who is both a data geek like me and an avid cyclist. You press Start on your phone, ride your bike, and press Stop when you're done. It gives graphs, maps, and all sorts of other goodies.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Getting to the data is then just a minor step: All of these apps seems to support exports in &lt;a href="http://code.google.com/apis/kml/documentation/"&gt;KML&lt;/a&gt; and &lt;a href="http://www.topografix.com/gpx.asp"&gt;GPX&lt;/a&gt;. Since these are actually just my rides, that data isn't all that interesting by itself. I know how I get to work.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;But with that data, I can create meta-analyses. For instance, how does my speed look across a given ride? Here's a ride I took from the Saturday Berkeley farmers market to Berkeley Bowl, our preferred grocery store.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;img src="http://2.bp.blogspot.com/-TGN4oTXuKTM/Tj66umcnWYI/AAAAAAAAAFk/HCrdgX3V2vk/s400/Screen%2BShot%2B2011-08-07%2Bat%2B9.15.23%2BAM.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5638149093271755138" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 388px; " /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="color:#0000ee;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;Green lines indicate places where I was faster than my average speed for that ride. Red lines indicate places where I was slower. I add in the Start and Stop pins, and also provide meta information about the ride off of the extra data in Cyclemeter's GPX file: total distance, average speed, and so forth. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Ruby made this work pretty straightforward. I use &lt;a href="http://nokogiri.org/"&gt;Nokogiri&lt;/a&gt; to parse a GPX file and calculate the velocity between subsequent points. Each velocity item has the coordinates and timestamps of the two points as well as the calculated velocity. I then use an ERB template for the KML I want to create. That ERB template sets up the styles and other items, and then uses the state variables to construct the line segments, the start/stop pins, and other items.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Eventually, I want to add arrows indicating the direction (which is more useful when you're looking at lots of overlapping routes), pins for the slowest point and the fastest point, and other items. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;That will do for individual routes, but I also plan to start aggregating my rides to show even more data.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-7585827251581858644?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/7585827251581858644/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2011/08/visualizing-my-bike-rides.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7585827251581858644'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7585827251581858644'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2011/08/visualizing-my-bike-rides.html' title='Visualizing My Bike Rides'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-TGN4oTXuKTM/Tj66umcnWYI/AAAAAAAAAFk/HCrdgX3V2vk/s72-c/Screen%2BShot%2B2011-08-07%2Bat%2B9.15.23%2BAM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-9093279933969704040</id><published>2011-07-24T14:18:00.001-07:00</published><updated>2011-07-26T13:25:44.155-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Processing'/><category scheme='http://www.blogger.com/atom/ns#' term='Using My Powers For ...'/><category scheme='http://www.blogger.com/atom/ns#' term='Data Wonk'/><title type='text'>Visualizing A Week Of 99.7</title><content type='html'>&lt;div&gt;&lt;a href="http://4.bp.blogspot.com/-KIrKOJ5vRbs/Ti7aT4eMH2I/AAAAAAAAAFc/yxPUS4NcTJ0/s1600/Screen%2Bshot%2B2011-07-26%2Bat%2B8.15.39%2BAM.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;/a&gt;&lt;div style="text-align: left;"&gt;When we're driving, Melissa and I often listen to Bay Area radio station &lt;a href="http://997now.radio.com/"&gt;99.7 FM&lt;/a&gt;. It specializes in dance-focused pop, club, and hip-hop songs. In other words: boppy, brainless music.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;But at any given point in time, it feels like they just replay the hits &lt;i&gt;du jour&lt;/i&gt;. How much diversity do they really have? I wanted to know.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The answer? Not much. A mere nine songs made up half the station's rotation in the week I measured. During that week, about half the songs you would have heard would have been one of those nine songs.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;img src="http://4.bp.blogspot.com/-O-5lULor2Fc/Ti7OITG2GEI/AAAAAAAAAFE/iLJWnkUVMbE/s400/Screen%2Bshot%2B2011-07-26%2Bat%2B7.23.11%2BAM.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5633666825850329154" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 300px; " /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;I also made an &lt;a href="http://www.obsessionwithfood.com/997viz.html"&gt;interactive version&lt;/a&gt; that requires a Java-enabled browser. It shows the song titles as you hover over them.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here are the top 9 songs:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Song Title&lt;/th&gt;&lt;th&gt;Artist&lt;/th&gt;&lt;th&gt;Times Played&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;The Edge of Glory&lt;/td&gt;&lt;td&gt;Lady Gaga&lt;/td&gt;&lt;td&gt;100&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;I Wanna Go&lt;/td&gt;&lt;td&gt;Britney Spears&lt;/td&gt;&lt;td&gt;75&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Till The World Ends&lt;/td&gt;&lt;td&gt;Britney Spears&lt;/td&gt;&lt;td&gt;69&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;How To Love&lt;/td&gt;&lt;td&gt;Lil Wayne&lt;/td&gt;&lt;td&gt;59&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Stereo Love&lt;/td&gt;&lt;td&gt;Gym Class Heroes&lt;/td&gt;&lt;td&gt;54&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Rolling In The Deep&lt;/td&gt;&lt;td&gt;Adele&lt;/td&gt;&lt;td&gt;53&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Written In The Stars&lt;/td&gt;&lt;td&gt;Tinie Tempah&lt;/td&gt;&lt;td&gt;52&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;The Lazy Song&lt;/td&gt;&lt;td&gt;Bruno Mars&lt;/td&gt;&lt;td&gt;46&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cheers (Drink to That)&lt;/td&gt;&lt;td&gt;Rihanna&lt;/td&gt;&lt;td&gt;39&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To gather the data for this chart, I used the &lt;a href="http://997now.radio.com/playlist/"&gt;station's published playlist&lt;/a&gt;, which lists 25 songs at a time. I set up a micro-instance on &lt;a href="http://aws.amazon.com/ec2/"&gt;Amazon.com's EC2&lt;/a&gt; to run a Ruby script every 45 minutes that fetched that playlist page, extracted the information I wanted (via &lt;a href="http://en.wikipedia.org/wiki/Regular_expression"&gt;regexes&lt;/a&gt; and the excellent &lt;a href="http://nokogiri.org/"&gt;Nokogiri library&lt;/a&gt;) and appended it to a file.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Then I set up a &lt;a href="http://nodejs.org/"&gt;node.js&lt;/a&gt; server* that returned a cleaned-up version of the raw playlist data I amassed. It removed duplicates caused not only by my data fetching script, whose 45-minute interval meant that there was always some degree of overlap, but also by a peculiarity in the data. Remixes on the site get two or more entries with the same timestamp, one for the original song and one for the remix title, and I collapsed those down into the original song. A remix of Britney Spears' "Till the World Ends" might be different in some ways than the original, but to me it counts as playing the same song. I've published &lt;a href="http://www.obsessionwithfood.com/997playlist.csv"&gt;the final dataset&lt;/a&gt; I used for this chart.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Along the way, I discovered two lacunae in the website's playlist — there are probably more — which affect the numbers a bit. As Nathan Yau says in his new book &lt;i&gt;&lt;a href="http://www.powells.com/biblio/1-9780470944882-0"&gt;Visualize This&lt;/a&gt;,&lt;/i&gt; "Just because it’s data doesn’t make it fact."&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Katy Perry's "Last Friday Night" and Pitbull's "Give Me Everything" got plenty of rotation on the station during this week but never showed up in the published playlist. I checked my raw data, the cleaned-up form, and did spot checks on the site whenever I heard one of those songs. They're just skipped. I assume this is some discrepancy in the database, since other songs from the same Katy Perry album show up in the list. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I don't know that it would change the numbers very much. There might be a more gradual drop-off from "Edge of Glory" to "I Wanna Go," but, if anything, the halfway mark would be closer in. The gap doesn't change the premise: 99.7 replays a lot of the same music.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For visualizing the data, I put it into a couple of tools -- a custom tool I'm writing as well as &lt;a href="http://www.r-project.org/"&gt;R&lt;/a&gt; -- but ultimately decided on &lt;a href="http://processing.org/"&gt;Processing&lt;/a&gt;, the big gun in any data visualizer's arsenal. Processing is a full programming language aimed specifically at making digital images, with an emphasis on visualization. I could both fully churn through and munge the semi-raw data and quickly visualize it, all with the same tool. And since Processing is basically Java with some handy utility methods, I'm already very comfortable with the language.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Inspired by Yau's book, which encourages a storytelling mindset, I decided to add visual cues and callouts for "points of interest" to my graphic: the most popular song, the cutoff line for the songs that made up fifty percent of the total, and the cutoff line for the the songs that made up eighty percent of the total.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Because Processing is a programming language, I drove everything off the data itself. While I obviously had to program the callouts I wanted on the chart, I don't have a line that says, "Draw 'Edge of Glory, Lady Gaga' at these coordinates." Instead I have a line that says, "draw the name of the song that got played the most next to the leftmost bar." I used the same mindset for all the callouts. Change the dataset, and the callouts change with it.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Once you have all this shiny, pretty data, you start looking at other ways to explore it. For instance, what's the average number of songs played in each hour? A little bit of modification to my Processing program, and I had a new chart ready to go from the same data.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;span class="Apple-style-span" style="-webkit-text-decorations-in-effect: underline; "&gt;&lt;img src="http://4.bp.blogspot.com/-KIrKOJ5vRbs/Ti7aT4eMH2I/AAAAAAAAAFc/yxPUS4NcTJ0/s400/Screen%2Bshot%2B2011-07-26%2Bat%2B8.15.39%2BAM.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5633680218998447970" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 300px; " /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;Then Melissa and I wondered if Bruno Mars' "The Lazy Song" gets played a lot more on weekends, since it's, well, about being lazy and deciding to do nothing with your day. Not really. As a rule, expect to hear it six to eight times a day at the moment, and not more on weekends.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://3.bp.blogspot.com/-gG521mcjrvs/Ti7VxlhkqQI/AAAAAAAAAFM/R3vwN00bIuw/s1600/Screen%2Bshot%2B2011-07-26%2Bat%2B7.56.28%2BAM.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://3.bp.blogspot.com/-gG521mcjrvs/Ti7VxlhkqQI/AAAAAAAAAFM/R3vwN00bIuw/s400/Screen%2Bshot%2B2011-07-26%2Bat%2B7.56.28%2BAM.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5633675231750301954" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 300px; " /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I have more ideas for this, but they're going to take a bit more data collection, so stay tuned for more in the coming weeks.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;*There was no need to use node.js here. It just gave me a chance to play with it for something deeper than the "Hello, World!" example.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-9093279933969704040?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/9093279933969704040/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2011/07/visualizing-week-of-997.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/9093279933969704040'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/9093279933969704040'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2011/07/visualizing-week-of-997.html' title='Visualizing A Week Of 99.7'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-O-5lULor2Fc/Ti7OITG2GEI/AAAAAAAAAFE/iLJWnkUVMbE/s72-c/Screen%2Bshot%2B2011-07-26%2Bat%2B7.23.11%2BAM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-6861539131045692334</id><published>2011-05-09T22:42:00.000-07:00</published><updated>2011-05-09T23:26:59.832-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Data Wonk'/><title type='text'>More With Google's WebGL Globe: Legen - Wait For It - Dary</title><content type='html'>In &lt;a href="http://programmingobsession.blogspot.com/2011/05/working-with-googles-webgl-globe.html"&gt;my last post&lt;/a&gt;, I talked about &lt;a href="http://www.chromeexperiments.com/globe"&gt;Google's WebGL data visualization globe&lt;/a&gt; and briefly mentioned the "legend" format for the data array.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I finally got a chance to do something with it, and it is both simple and powerful. If you're in legend mode, your big data array is four pieces of data at a time, not three. The first two pieces are latitude and longitude. The third is the magnitude of the line that will be drawn (divided by 200 to compensate for their multiplying by 200). So what is the fourth value? &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Anything you want. When you specify legend mode, the color function you pass in to the globe's constructor gets that fourth value for each point on the globe. That in turn allows you to define the color of the line based on something other than magnitude (the default).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;What that really means is that you get an extra dimension in your data. Height is always height, but legend mode allows you to add non-height information based on the color of the line.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Google's initial example — s&lt;a href="http://data-arts.appspot.com/globe-search"&gt;earch language by volume&lt;/a&gt; — is a great example. The color of the line comes from the dominant language of the area. Looking at their globe, you can immediately figure out where the high search volume comes from (big cities, mainly) just by looking for the tall lines. You can also see which languages dominate search in a given area. English (blue) covers the United States and the United Kingdom. French (light green) covers France and also Quebec. Portugese (dark green) dominates Portugal and Brazil, and also Madeira. Likewise, the Canary Islands are yellow, because they're a part of Spain.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For a new visualization at work I did with the globe (as usual, NDAs make me cautious about giving more exact details), people at my studio wanted to see the height of the line represent the number of events at a given location. But they wanted the color to represent whether the average data from that spot represented a "good" or "bad" user experience. Little red lines would be unfortunate, but might not get flagged as high priority. Big red lines would be a problem, because it would mean that a lot of players were having a bad experience. Fortunately, there weren't any of those, but there were some long yellow lines, which suggests an area where we could improve the player's interaction with our game.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To get that view, I set the globe to legend mode and set the magnitude field of each point to the sample size. Then I set the fourth data point to a very heterogeneous number that represented the user experience. My color function looks at that number and puts it into a bucket. Good returns green, bad returns red, and a middle ground returns yellow.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;That means each line on my globe gives three pieces of information: location on the planet, sample size from that location, and quality of user experience from that location. It's easy to spin the globe and look for red hot spots. It's also just a pleasure to play with the visual, even though you can find the worst spots pretty quickly.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;One person saw my work and suggested adding a calculation that would make the line more or less red, for instance, depending on how far over the bad threshold it went. Bright red would mean a really bad experience; dim red would be right at the line. I guess that would give us three and a half data points. Before, we knew it was bad. Now, we'll know how bad it is.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Legend mode makes this all possible.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-6861539131045692334?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/6861539131045692334/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2011/05/more-with-googles-webgl-globe-legen.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6861539131045692334'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6861539131045692334'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2011/05/more-with-googles-webgl-globe-legen.html' title='More With Google&apos;s WebGL Globe: Legen - Wait For It - Dary'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-2326379215817659769</id><published>2011-05-05T19:44:00.000-07:00</published><updated>2011-05-06T06:02:04.796-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='Data Wonk'/><title type='text'>Working With Google's WebGL Globe</title><content type='html'>&lt;div&gt;&lt;br /&gt;Today, Google released a &lt;a href="http://data-arts.appspot.com/globe-search"&gt;data visualization&lt;/a&gt; that shows search volume by language across the globe. It uses WebGL, so you'll need a recent version of Chrome and decent video drivers to see it.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;a href="http://1.bp.blogspot.com/-dVqNWk8XtsQ/TcNhcgHxLiI/AAAAAAAAAC8/IMOeu1Z3924/s1600/Screen%2Bshot%2B2011-05-05%2Bat%2B7.46.35%2BPM.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 307px;" src="http://1.bp.blogspot.com/-dVqNWk8XtsQ/TcNhcgHxLiI/AAAAAAAAAC8/IMOeu1Z3924/s400/Screen%2Bshot%2B2011-05-05%2Bat%2B7.46.35%2BPM.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5603429503665778210" /&gt;&lt;/a&gt;&lt;br /&gt;It's different than the normal "things on a globe" visualizations you see in — for instance — Google Earth, because it incorporates height as an additional dimension. Google Maps and Google Earth give flat perspectives: You can only guess magnitude based on clusters of the familiar red, upside-down teardrops.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;By itself, the visualization would be a five-minute folderol to enjoy on a lunch break. But Google's Data Arts team released all the source. Take your own data, muck with it a bit, and you too can have an interactive globe dripping in an oh-so-modern, shades-of-black aesthetic.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I took the bait.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Over the course of today, I took some of our game analytics data and built a local WebGL globe visualization. We have an established workflow for creating latitude/longitude tables from our data — this isn't the first map visualization I've done — and I built on top of that. Once I extracted the information I wanted, I wrote a quick Ruby script that converts the exported data into the format their code looks for.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I can't share a link with you — it's on our internal network — and I can't tell you what I mapped. But I can tell you that it got lots of oohs and aahs in the office.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I can also tell you that it wasn't a simple "replace their data with our data" exercise. If you're planning on doing something with the tool, here are a few things I figured out by trial and error.&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;i&gt;Note: This has since been fixed. &lt;/i&gt;Don't rely on &lt;a href="https://github.com/dataarts/dat.globe/blob/master/README.md"&gt;their README&lt;/a&gt;: It's wrong. At least, the JSON format is. It's much better to read the &lt;a href="https://github.com/dataarts/dat.globe/blob/master/globe/globe.js"&gt;globe.js file&lt;/a&gt; to see what it wants, though that requires you to know JavaScript. Rather than a complex set of nested arrays, the code prefers one long array with latitude, longitude, and magnitude strung together like beads on a string. (There's an optional "legend" mode that requires a fourth point, but I haven't played with it. I assume it lets you define different data series. Their &lt;code&gt;addData&lt;/code&gt; method takes the data block and a set of options, and one of those options is &lt;code&gt;format&lt;/code&gt;: The default is &lt;code&gt;magnitude&lt;/code&gt; — three data points — and you can specify &lt;code&gt;legend&lt;/code&gt; — four data points.)&lt;/li&gt;&lt;li&gt;A portion of their code multiplies the magnitude by 200 to ensure the small numbers in their data — percentages, I suppose — become big enough for bars that reach high into the sky. For our particular data set, I had to divide the number by 200 in order to get the bars to be correct once they multiply it by 200.&lt;/li&gt;&lt;li&gt;The default coloring of the lines relates to the height, but it looks like you can pass a custom "get the color" function for different logic.&lt;/li&gt;&lt;li&gt;Their source code relies on a top-level directory on your website named /globe for the location of the map image that wraps onto the sphere. You can change that easily enough. Search for "imgDir" in the source.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-2326379215817659769?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/2326379215817659769/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2011/05/working-with-googles-webgl-globe.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/2326379215817659769'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/2326379215817659769'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2011/05/working-with-googles-webgl-globe.html' title='Working With Google&apos;s WebGL Globe'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-dVqNWk8XtsQ/TcNhcgHxLiI/AAAAAAAAAC8/IMOeu1Z3924/s72-c/Screen%2Bshot%2B2011-05-05%2Bat%2B7.46.35%2BPM.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-379197212113218052</id><published>2011-03-15T09:19:00.000-07:00</published><updated>2011-03-15T09:32:38.766-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><title type='text'>More Readable Hash Member Access In Ruby</title><content type='html'>I've been doing a lot of work with the &lt;a href="https://github.com/grempe/amazon-ec2"&gt;amazon-ec2&lt;/a&gt; Ruby library for working with Amazon Web Services. I've also been using &lt;a href="http://en.wikipedia.org/wiki/YAML"&gt;YAML&lt;/a&gt; a lot for config files. But both those systems return big nested hashes with string keys, so my code ends up looking like this: &lt;code&gt;config['database']['security_group']&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;That gets tiresome to read. To fix that within these scripts, I extended the Hash object by adding an implementation of &lt;code&gt;method_missing?&lt;/code&gt;, which Ruby calls when you try to execute a method on an object and it doesn't exist. My implementation says to try and use the method name as a key into the hash.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Hash&lt;br /&gt;   def method_missing(method_name, *args, &amp;block)&lt;br /&gt;      fetch(method_name.to_s,nil)&lt;br /&gt;   end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Now the same code from before looks like this: &lt;code&gt;config.database.security_group&lt;/code&gt;. Ruby tries to call the "database" method of the config object (a Hash), fails, and then my extension to the class looks up a key named 'database' in the hash and returns that.&lt;br /&gt;&lt;br /&gt;I find this much easier to read, particularly when it's repeated throughout the file. Of course, it assumes the keys in the hash are strings, but in this script they are (so my extension is only added for that script).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-379197212113218052?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/379197212113218052/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2011/03/more-readable-hash-member-access-in.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/379197212113218052'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/379197212113218052'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2011/03/more-readable-hash-member-access-in.html' title='More Readable Hash Member Access In Ruby'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-7863683600540932793</id><published>2011-01-16T09:06:00.000-08:00</published><updated>2011-01-16T16:48:33.404-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Neat'/><category scheme='http://www.blogger.com/atom/ns#' term='Automation'/><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='Philosophy'/><title type='text'>One-Button Loadtesting With EC2</title><content type='html'>I recently realized a minor ambition. &lt;br /&gt;&lt;br /&gt;As I've said &lt;a href="http://programmingobsession.blogspot.com/2009/11/learning-jmeter.html"&gt;before&lt;/a&gt;, EA's loadtesting group doesn't meet all my needs: They come in near the end of a project and test the nearly-finished system, which means that it's a big headache all around. Fixing performance issues means rummaging through months &amp;mdash; or even years &amp;mdash; of code to try and suss out issues they're seeing. Also, the mere act of getting them up and running is usually a big disruption for one or more members of your team.&lt;br /&gt;&lt;br /&gt;I had loadtesting scripts running already, but they suffered in a few ways. JMeter is good at what it does, but I want easier programmatic control of the scripts (especially for measuring things it's not so good at, such as the lag time on asynchronous processes under load). Also, I didn't have a loadtesting environment. I was running the scripts on a dev server against that same server. I could have put together an environment of multiple servers, but that would incur expense to the studio for machines that were largely idle.&lt;br /&gt;&lt;br /&gt;Then I read an article somewhere about a company that uses &lt;a href="http://aws.amazon.com/ec2/"&gt;EC2&lt;/a&gt; as a dynamic loadtest environment. Yes, I thought, that's what I want.&lt;br /&gt;&lt;br /&gt;Off and on over the last few weeks, I put together a Ruby script that can, with a single command, set up a full environment in Amazon's cloud-based servers. That one command instantiates an &lt;a href="http://aws.amazon.com/rds/"&gt;RDS&lt;/a&gt; database, an EC2 server that will act as our application server, and another EC2 server that acts as an instance for running &lt;a href="http://grinder.sourceforge.net/"&gt;The Grinder&lt;/a&gt;, a loadtesting tool that another team in the studio is using and that I liked for its extensibility. That one command also builds a custom version of our war file (to handle all the dynamic addresses you get from EC2/RDS), sets up access permissions between all the servers, and runs checks to make sure the whole environment is ready to go.&lt;br /&gt;&lt;br /&gt;A second command fires off the loadtest itself, running the test script through The Grinder and collecting the results back to the local machine.&lt;br /&gt;&lt;br /&gt;Finally, a third command shuts the whole system down. I do have one command that runs all three steps, but at the moment I tend to run each one manually. &lt;br /&gt;&lt;br /&gt;Each run takes less than an hour for now, so the cost to the studio is on the order of $.28 per loadtesting run. Not something you'd want to run continually, but certainly something you could run once a day. As we move along in development, we'll probably need to spend more to set up bigger, more realistic environments, but that will also be when the studio has more budget for my project. The environments we need will scale up alongside the money we have.&lt;br /&gt;&lt;br /&gt;One of the key pieces that I set up was the concept of a profile. A config file lets you specify which script to run and how many of each item to set up. So, for my first profile, I just set up one each of the different servers. But you could imagine some profile later down the road that sets up 2 of each machine with some sort of autoscaling system. And you could imagine one much further down the road that sets up something approximating our production environment and runs tests against it. All that is mostly supported.&lt;br /&gt;&lt;br /&gt;The advantages to this system are huge. First, it makes real loadtesting feasible even early on in our project. But it also empowers developers to do rapid iteration on performance fixes without having to push them live and see if they make a difference. See a problem in the results, figure out a fix, run the script again to test. Repeat and then release when you can prove that your fix makes a difference. Because each script fires up its own environment, I'll be able to distribute performance fix work to my team.&lt;br /&gt;&lt;br /&gt;This ad hoc loadtesting tool is already proving its worth, and I've only just started employing it. I can't wait to see how effective it is going forward.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-7863683600540932793?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/7863683600540932793/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2011/01/one-button-loadtesting-with-ec2.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7863683600540932793'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7863683600540932793'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2011/01/one-button-loadtesting-with-ec2.html' title='One-Button Loadtesting With EC2'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-6063508917440538731</id><published>2010-11-23T07:01:00.000-08:00</published><updated>2010-11-23T07:38:19.221-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Neat'/><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Spring'/><title type='text'>Dynamic Feature Disabling</title><content type='html'>Often, when I'm rolling out a new, substantial feature, I add a config file property that marks it as enabled or disabled. This lets the team shut it down quickly if it's causing problems. It's a good practice, overall.&lt;br /&gt;&lt;br /&gt;Keeping the status in a config file is easy to maintain, but it's problematic. Being able to "switch off" a feature in a config file really means, for most Java deployments, rebuilding and redeploying the war file, which can take a while if you're including a bunch of big libraries.&lt;br /&gt;&lt;br /&gt;Recently, inspired by two pieces I read about how disabling features helps scale a site and mitigate risk, I came up with an enhancement to my older idea, and it's one of those brain-dead-obvious changes that I wish I had done before: A feature's enabled or disabled status now lives in the database.&lt;br /&gt;&lt;br /&gt;I have a simple table (with Hibernate object and service) that stores a feature name, its current enabled status, and the date on which that status changed. Any other service or controller in the system can query that service to see if a given feature is enabled. (Since I run in the Spring framework, access to the service is a simple matter.)&lt;br /&gt;&lt;br /&gt;What that means practically is that I can disable an entire subsystem across all instances of my application simply by updating a database row. (In the live system, these will probably be cached and refreshed every few minutes so that incoming requests aren't slowed down.) It also means I can get the status of the systems across an entire bank of servers with a simple query.&lt;br /&gt;&lt;br /&gt;I put it in as groundwork for one feature &amp;mdash; capturing real incoming traffic to a site to then play back as loadtesting scripts &amp;mdash; but quickly back-ported a few other systems that have single points of entry. Our profiling system, for instance, can now be turned off application-wide simply by setting the enabled status of "Profiling" to false.&lt;br /&gt;&lt;br /&gt;Of course, once you have subsystems that have a dynamic mechanism for checking their enabled/disabled status, it's a small step to enabling/disabling on a per-user basis. Which means we can gradually roll out new features, checking the load on the system at each pass and fixing bugs.&lt;br /&gt;&lt;br /&gt;It's not an earth-shattering idea, but I was immediately entranced by the power it gives to my system.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-6063508917440538731?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/6063508917440538731/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/11/dynamic-feature-disabling.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6063508917440538731'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6063508917440538731'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/11/dynamic-feature-disabling.html' title='Dynamic Feature Disabling'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-781478733701264416</id><published>2010-10-02T10:05:00.000-07:00</published><updated>2010-10-02T17:17:53.274-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='Automation'/><category scheme='http://www.blogger.com/atom/ns#' term='Philosophy'/><title type='text'>Integration Tests</title><content type='html'>I've been a fan of &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit tests&lt;/a&gt; for a couple of years now. Once I buckled down and wrote some, I realized how powerful they could be. They give you more stability out of the gate and they provide good regression tests. They allow me, the developer, to keep moving forward in tasks and minimize the time I spend going backward to debug or test older functions.&lt;br /&gt;&lt;br /&gt;But I've always held the stance that unit tests shouldn't test things such as interactions with &lt;a href="http://www.hibernate.org/"&gt;Hibernate&lt;/a&gt; or &lt;a href="http://www.springsource.org/"&gt;Spring&lt;/a&gt;. Those are well-tested frameworks with strong community support. Writing unit tests that touch those layers always struck me as a waste of time.&lt;br /&gt;&lt;br /&gt;That said, most server-side Java code ends up moving into those layers. Virtually everything I write ends up talking to a database. And the server code itself is a loosely knit tapestry of services that chat with each other. That code can certainly have bugs &amp;mdash; incorrect queries, edge conditions, bad assumptions, whatever. So how do I get tests against it?&lt;br /&gt;&lt;br /&gt;Integration tests. Like unit tests, integration tests demonstrate that function A returns result X given inputs Y and Z. The difference is that in an integration test, you are testing the interaction between systems versus the simple in and out of a self-contained function.&lt;br /&gt;&lt;br /&gt;I finally decided that I wasn't getting the test coverage I wanted with just unit tests. I was finding subtle bugs tucked away in database calls and service-to-service communication. Mock objects &amp;mdash; code that presents the expected interface to a layer without providing the full functionality &amp;mdash; only get you part of the way. If your mock object is keeping information in a local map instead of hitting the database, you're not testing that the query to the database returns the right thing.&lt;br /&gt;&lt;br /&gt;Once I decided to incorporate integration tests, however, I went down a bit of rabbit hole. Your services need to talk to each other, so you need to wire them up to each other. But in the real, running code, I use Spring to manage all that. Fortunately, I can use Spring in the test environment, too. And Spring allows me to have a supplemental bean configuration file that overrides the production-code config file. So, for instance, I can have a bean named "telemetryService" that overrides the bean of the same name in the main config file. The test version doesn't actually send telemetry information. It effectively becomes a mock object. (Though in that particular case, it's done with a simple boolean.) I have an S3 service layer in the test config file that points to a test S3 bucket instead of our development one. Any beans that aren't overridden are pulled from the main file.&lt;br /&gt;&lt;br /&gt;My integration tests do have to call a method to set up that configuration, however. Since the Spring config doesn't change at runtime, I have a static utility method that checks to see if the configuration is set up and, if it isn't, sets it up. This violates the principle that a test should not rely on existing state from another test, but that configuration load is time consuming.&lt;br /&gt;&lt;br /&gt;But services don't just talk to each other. They also talk to the database. You don't want them to point to a database you actually use, so you need a test database. And that test database needs to a) be kept up to date, b) have some test data, and c) not let data linger (to ensure that no test ends up passing because of the coincidence of some other test running before it).&lt;br /&gt;&lt;br /&gt;For the last two requirements, I incorporated &lt;a href="http://www.dbunit.org"&gt;DBUnit&lt;/a&gt;. DBUnit can do unit testing against the database, but I don't really need its form of that. Because I'm using Spring to set up my app in test mode, all my services can continue to use Hibernate for database work. (They can also use my JRuby-based query files.) But DBUnit offers two key services: It can take a data file of test data and load the database with it, and it can wipe any desired tables clean between test runs. When a test suite starts, it calls a method that ensures the Spring configuration is read (if it hasn't already been) and wipes/reloads the test database. When a test suite starts, I know that there is predictable data in the database and that that's the only data in the database. &lt;br /&gt;&lt;br /&gt;What about between tests, though? I could do a wipe and reload around every test, but that's time-consuming. Instead, I have a setup method that configures a transaction and a teardown method that rolls back that transaction. All of this means that developers have to remember to do all this when writing an integration test, which I dislike, but I hope to integrate some tools that will automate that.&lt;br /&gt;&lt;br /&gt;My test database also needs to be current with the latest schema. For that, I incorporated &lt;a href="http://autopatch.sourceforge.org"&gt;Autopatch&lt;/a&gt;, which will automatically get your database up to date when a code base runs against it.&lt;br /&gt;&lt;br /&gt;So far, so good. But integration tests take a while to run, and I worry that developers might, faced with progressively longer test runs, disable tests on local builds. To keep our tests spry, I separate integration tests so that they're only run if you tell &lt;a href="http://buildr.apache.org"&gt;Buildr&lt;/a&gt; you're in the "longtests" environment. (That environment also specifies the test database properties.) The build system, which runs within minutes of any check-in, always runs them, however, so even a developer who gets hasty has an automatic monitoring.&lt;br /&gt;&lt;br /&gt;All of this took three solid days to get working properly, but now that's it running, I got exactly what I wanted: the ability to have much deeper test coverage. The other day, I wrote a test that called service methods that altered the database and moved files around on S3. At each step in the chain, I could check the state of each component and verify that it had worked succesfully. And I knew that it was executing real queries and running real code. No mock objects.&lt;br /&gt;&lt;br /&gt;The ability to have deeper test coverage means I've set myself a goal of writing those tests. Every feature I work on now has tests against it, even if they require database work or multiple services. And when I find a bug in my existing code, I force myself to write a test to reproduce it and then work on the code until the test passes. That way I get free regression testing whenever a check-in happens.&lt;br /&gt;&lt;br /&gt;I have noticed one negative aspect, however. Unit tests, by their very nature, force you to write smaller and smaller methods that are easier to test. With no limit on the amount of activity you can do in an integration test, however, I find that my methods aren't channeled into smaller pieces. I try and do that because I know that ultimately produces more maintainable code, but my integration test system doesn't give an almost automatic push the way unit tests do.&lt;br /&gt;&lt;br /&gt;Big methods or not, I still get to run a complete suite of extensive, realistic tests with a single command. That's pretty powerful.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-781478733701264416?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/781478733701264416/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/10/integration-tests.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/781478733701264416'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/781478733701264416'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/10/integration-tests.html' title='Integration Tests'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-5888984168165420172</id><published>2010-09-28T22:25:00.000-07:00</published><updated>2010-09-28T23:01:49.277-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Neat'/><category scheme='http://www.blogger.com/atom/ns#' term='Philosophy'/><title type='text'>Config Files As Programs</title><content type='html'>While working on a few recent tasks, I've hit upon a small but powerful technique: My configuration files are actually programs in their own right. &lt;br /&gt;&lt;br /&gt;Expression languages of some form are &lt;em&gt;de rigueur&lt;/em&gt; in modern frameworks. Especially in server code, where you need to deploy the same code base to different servers and have them point to different databases, mail servers, or whatever. In Spring, for instance, you can write something like &lt;code&gt;${project.database.url}&lt;/code&gt; and it will assume you mean to use not the literal string but the value of the &lt;code&gt;project.database.url&lt;/code&gt; property, which is probably different in your dev environment and your production environment.&lt;br /&gt;&lt;br /&gt;But my technique is vastly more powerful. I'm sure others have thought of it, too, but it sort of snuck up on me.&lt;br /&gt;&lt;br /&gt;The first time I used this trick was when &lt;a href="http://programmingobsession.blogspot.com/2010/01/polyglot-programming.html"&gt;I put my work's SQL into Ruby scripts&lt;/a&gt;. I did that for readability's sake, but as I added more queries, I began to do the things one does does with programs. I refactored two similar queries into a method that took a differentiating argument and returned the query. I declared constants to hold common values. I started to ponder a Ruby module that I could import for various utility functions. The end result from the Java code's view was a set of query definitions. But within the query definition itself, I had normal programming tools at my disposal.&lt;br /&gt;&lt;br /&gt;On a personal project (which I hope to write about soon), I decided to use &lt;a href="http://en.wikipedia.org/wiki/Yaml"&gt;YAML&lt;/a&gt; for the config files. I've gotten used to the format for config files, thanks to my work with &lt;a href="http://code.google.com/appengine/"&gt;AppEngine&lt;/a&gt;, and Ruby can easily work with it. My original idea for the config file was just fixed dates and times when I wanted my script to wake up and record an Internet radio stream. But then I realized that I wanted to record some streams on every weekday or on the first Sunday of the month. &lt;br /&gt;&lt;br /&gt;So I thought, what if, before I feed the file to the YAML parser, I first run it through ERB, Ruby's powerful templating system? As long as the YAML parser sees something that looks like YAML, it doesn't matter how it gets there, right?&lt;br /&gt;&lt;br /&gt;Right. I defined a method in my config file &amp;mdash; in my config file! &amp;mdash; that would return the next weekday after the given date. Then, when defining the value for a field, I called that method to calculate the value. My "YAML" file looks something like: &lt;code&gt;start_date: &amp;lt;%next_weekday.strftime('%Y-%m-%d')%&amp;gt; 18:00&lt;/code&gt;. Which won't give you anything useful from the YAML parser. But run it through the ERB parser, and the YAML parser sees &lt;code&gt;start_date: 2010-09-28 18:00&lt;/code&gt;, which is perfectly valid YAML syntax and is what you intend for the value to be.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-5888984168165420172?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/5888984168165420172/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/09/config-files-as-programs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/5888984168165420172'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/5888984168165420172'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/09/config-files-as-programs.html' title='Config Files As Programs'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-2778471676467279617</id><published>2010-09-03T09:54:00.000-07:00</published><updated>2010-09-03T11:04:37.387-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='Using My Powers For ...'/><title type='text'>Words With Friends Bingos, Redux</title><content type='html'>In &lt;a href="http://programmingobsession.blogspot.com/2010/09/optimize-for-bingos-in-words-with.html"&gt;my last post&lt;/a&gt;, I described writing a program to find the five-letter combinations most likely to yield a bingo in Words With Friends.&lt;br /&gt;&lt;br /&gt;Then I realized there was a subtle bug. &lt;br /&gt;&lt;br /&gt;Consider the word &lt;em&gt;abreast&lt;/em&gt;. When you look at how Ruby's &lt;code&gt;combination&lt;/code&gt; method splits it up, you'll see two entries equivalent to STARE, because there are two a's. This is correct from a programming view, but from a player's perspective, it doesn't matter. Having STARE is sufficient to get to &lt;em&gt;abreast&lt;/em&gt;. You don't view the a's as different.&lt;br /&gt;&lt;br /&gt;Here is the new word list with the corrected code. TEARS and TIERS are still your best bet, but the list has shifted a bit and the numbers are different.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;aerst 1295&lt;br /&gt;eirst 1237&lt;br /&gt;aeirs 988&lt;br /&gt;aeist 984&lt;br /&gt;einst 983&lt;br /&gt;einrs 972&lt;br /&gt;eorst 949&lt;br /&gt;aelrs 940&lt;br /&gt;eerst 920&lt;br /&gt;aeins 854&lt;br /&gt;aeirt 816&lt;br /&gt;aenrs 805&lt;br /&gt;aenst 799&lt;br /&gt;eeirs 792&lt;br /&gt;aelst 788&lt;br /&gt;eilst 782&lt;br /&gt;aeint 765&lt;br /&gt;aeils 765&lt;br /&gt;ainst 751&lt;br /&gt;eilrs 748&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And here's the corrected script.&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;# find the 5-letter combos most represented in 7- or 8-letter words&lt;br /&gt;&lt;br /&gt;wordlist = ARGV[0]&lt;br /&gt;&lt;br /&gt;combos_to_count = Hash.new(1)&lt;br /&gt;&lt;br /&gt;File.open(wordlist,"r") do |file|&lt;br /&gt;   line = file.gets&lt;br /&gt;   while(line)&lt;br /&gt;      line = file.gets      &lt;br /&gt;      if line then&lt;br /&gt;          line.chomp!&lt;br /&gt;          next if line.length != 7 &amp;&amp; line.length != 8&lt;br /&gt;          chars = line.split(//)&lt;br /&gt;          unique_combos = []&lt;br /&gt;          chars.combination(5) do |combo| &lt;br /&gt;              combo.sort! {|a,b| a &amp;lt;=&amp;gt; b}&lt;br /&gt;              unique_combos &amp;lt;&amp;lt; combo.join&lt;br /&gt;          end&lt;br /&gt;          unique_combos.uniq!&lt;br /&gt;          unique_combos.each {|combo| combos_to_count[combo] = combos_to_count[combo] + 1}&lt;br /&gt;      end&lt;br /&gt;   end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;sorted_combos = combos_to_count.sort {|a,b| b[1] &amp;lt;=&amp;gt; a[1]}&lt;br /&gt;(0...20).each {|num| puts sorted_combos[num][0] + " " + sorted_combos[num][1].to_s}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-2778471676467279617?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/2778471676467279617/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/09/words-with-friends-bingos-redux.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/2778471676467279617'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/2778471676467279617'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/09/words-with-friends-bingos-redux.html' title='Words With Friends Bingos, Redux'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-5163055598946766027</id><published>2010-09-03T07:04:00.000-07:00</published><updated>2010-09-03T11:05:36.279-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='Using My Powers For ...'/><title type='text'>Optimize For Bingos In Words With Friends</title><content type='html'>&lt;em&gt;See the updated version &lt;a href="http://programmingobsession.blogspot.com/2010/09/words-with-friends-bingos-redux.html"&gt;here.&lt;/a&gt;&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;Somewhere in my library of books is one called &lt;a href="http://www.powells.com/biblio/1-9780671042189-10"&gt;&lt;em&gt;Everything Scrabble&lt;/em&gt;&lt;/a&gt;, a guide to improving your Scrabble game. I read through it a few years back and have probably forgotten half its contents, but I do remember the author's comment that highly ranked Scrabble players often swap tiles in an effort to increase their chances of hitting a bingo (a word that uses all seven letters on your tray).&lt;br /&gt;&lt;br /&gt;I've been playing &lt;a href="http://newtoyinc.com/wp/"&gt;Words With Friends&lt;/a&gt; (screen name: linechef) and have been thinking about that Scrabble strategy. Bingoing is less of an advantage in WWF. They're only worth 35 points instead of 50, and they often open up large swaths of bonus tiles for your opponent to gobble up. But they are usually worth it if you can hit a bonus tile yourself.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;Everything Scrabble&lt;/em&gt; provides the scoop on which five tiles you should keep in your tray to maximize bingoing, but my mind let that knowledge go into the abyss. So I decided to write a program to rediscover it.&lt;br /&gt;&lt;br /&gt;You can imagine how the program works: Go through every seven- and eight-letter word in the Words With Friends dictionary, the &lt;a href="http://code.google.com/p/dotnetperls-controls/downloads/detail?name=enable1.txt&amp;can=2&amp;q="&gt;ENABLE wordlist&lt;/a&gt;, and find every unique combination of five letters in it. Keep track of how many copies of that combination you've seen throughout the dictionary, and then sort.&lt;br /&gt;&lt;br /&gt;Let's start with the answers first. Here are the top 20 combinations, along with the number of times they occurred (the ENABLE list has 172,823 words):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;aerst 2413&lt;br /&gt;eirst 2199&lt;br /&gt;einst 1780&lt;br /&gt;eorst 1698&lt;br /&gt;eerst 1646&lt;br /&gt;einrs 1596&lt;br /&gt;aeist 1587&lt;br /&gt;aeirs 1484&lt;br /&gt;aelrs 1468&lt;br /&gt;aelst 1350&lt;br /&gt;aenst 1346&lt;br /&gt;eilst 1338&lt;br /&gt;eiprs 1286&lt;br /&gt;aeirt 1273&lt;br /&gt;aeprs 1266&lt;br /&gt;aenrs 1258&lt;br /&gt;aeins 1245&lt;br /&gt;einrt 1238&lt;br /&gt;deirs 1233&lt;br /&gt;ainst 1232&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;And here's the Ruby code. Ruby 1.8.7 added a &lt;code&gt;combination&lt;/code&gt; method to the &lt;code&gt;Array&lt;/code&gt; class, which makes this program tiny. You tell it how big you want each subset to be, and you pass it a block which takes each subarray as an argument.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;wordlist = ARGV[0]&lt;br /&gt;&lt;br /&gt;combos_to_count = Hash.new(1)&lt;br /&gt;&lt;br /&gt;File.open(wordlist,"r") do |file|&lt;br /&gt;   line = file.gets&lt;br /&gt;   while(line)&lt;br /&gt;      line = file.gets      &lt;br /&gt;      if line then&lt;br /&gt;          line.chomp!&lt;br /&gt;          next if line.length != 7 &amp;&amp; line.length != 8&lt;br /&gt;          chars = line.split(//)&lt;br /&gt;          chars.combination(5) do |combo|&lt;br /&gt;             combo.sort! {|a,b| a &amp;lt;=&amp;gt; b}&lt;br /&gt;             combo_str = combo.join&lt;br /&gt;             combos_to_count[combo_str] = combos_to_count[combo_str] + 1&lt;br /&gt;          end&lt;br /&gt;      end&lt;br /&gt;   end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;sorted_combos = combos_to_count.sort {|a,b| b[1] &amp;lt;=&amp;gt; a[1]}&lt;br /&gt;(0...20).each {|num| puts sorted_combos[num][0] + " " + sorted_combos[num][1].to_s}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Once you have the code, you can start asking other questions. Is the list different for words with exactly seven letters? A little bit:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;aerst 609&lt;br /&gt;eirst 493&lt;br /&gt;eorst 450&lt;br /&gt;aelrs 413&lt;br /&gt;eerst 404&lt;br /&gt;einst 385&lt;br /&gt;aeprs 379&lt;br /&gt;einrs 348&lt;br /&gt;aelst 346&lt;br /&gt;eilst 335&lt;br /&gt;acers 331&lt;br /&gt;aeirs 330&lt;br /&gt;aenst 327&lt;br /&gt;aeist 322&lt;br /&gt;deirs 320&lt;br /&gt;aders 318&lt;br /&gt;eiprs 317&lt;br /&gt;eilrs 297&lt;br /&gt;aerss 296&lt;br /&gt;deers 295&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Note that there are only 676 permutations of the other two letters you could fit in your tray, so with STARE you can bingo with almost any combination. I leave learning those 609 words as an exercise for the reader.&lt;br /&gt;&lt;br /&gt;What about a different dictionary? Here's the same run I first did but with the Scrabble Official Dictionary, Third Edition.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;aerst 2395&lt;br /&gt;eirst 2182&lt;br /&gt;einst 1759&lt;br /&gt;eorst 1670&lt;br /&gt;eerst 1628&lt;br /&gt;einrs 1581&lt;br /&gt;aeist 1562&lt;br /&gt;aeirs 1473&lt;br /&gt;aelrs 1458&lt;br /&gt;aelst 1335&lt;br /&gt;aenst 1327&lt;br /&gt;eilst 1317&lt;br /&gt;eiprs 1278&lt;br /&gt;aeprs 1258&lt;br /&gt;aeirt 1257&lt;br /&gt;aenrs 1239&lt;br /&gt;aeins 1232&lt;br /&gt;ainst 1224&lt;br /&gt;einrt 1219&lt;br /&gt;deirs 1218&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Of course, some of this is common sense: EST gives you the superlative of many words. Likewise, ER gives you the comparative along with the prefix RE and the "person who does" suffix. Those four letters plus vowels other than U and Y form top contenders in many of the runs. &lt;br /&gt;&lt;br /&gt;If you're playing WWF (or Scrabble), work to keep STARE in your tray, playing or swapping other tiles, until you get a bingo. Placing it on the board, of course, is a different matter.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-5163055598946766027?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/5163055598946766027/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/09/optimize-for-bingos-in-words-with.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/5163055598946766027'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/5163055598946766027'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/09/optimize-for-bingos-in-words-with.html' title='Optimize For Bingos In Words With Friends'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-4063338579871396638</id><published>2010-08-25T08:25:00.000-07:00</published><updated>2010-08-25T13:25:28.620-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><title type='text'>Rails Scaffolding In Java</title><content type='html'>I recently needed to add a new business object to a system I'm developing at work, and it was a bit of a drag.&lt;br /&gt;&lt;br /&gt;If you follow any of the normal Java patterns, you probably know what I'm talking about. You need the file for your business object. You need a service class responsible for returning instances or sets of instances from the DAO layer. You need a SQL file that defines the database table the business objects will be stored in. In our case, we also have &lt;a href="http://programmingobsession.blogspot.com/2010/01/polyglot-programming.html"&gt;queries in resource files&lt;/a&gt;, so we need a file for that as well. You need to edit your Spring config files to point to the new service. And so on. Your environment probably has some overlap with mine, and probably requires pieces mine doesn't.&lt;br /&gt;&lt;br /&gt;You can simplify a lot of this, of course. You can make a service base class with Java Generics that will give you a lot of the type-safe methods you'd want. IDEs will let you set up templates, but you'll still have to click through a few menu options to get you the files you need. And the more files, the more clicks. And I like running an IDE-neutral team, so I wouldn't want to do something specific to one workflow.&lt;br /&gt;&lt;br /&gt;I wanted a better way. Specifically, I wanted what &lt;a href="http://rubyonrails.org/"&gt;Rails&lt;/a&gt; provides. You type a command at the command line, and you get all the files you need for working with the object. (XCode offers similar functionality.)&lt;br /&gt;&lt;br /&gt;And I thought, "Well, why not?" &lt;a href="http://programmingobsession.blogspot.com/2010/01/buildr.html"&gt;My build system&lt;/a&gt; is written in Ruby, and Ruby's ERB templating system is built in to the language.&lt;br /&gt;&lt;br /&gt;It took about an hour to get the main system up and running. I now type &lt;code&gt;buildr :biz_object object=com.ea.foo.TestTest&lt;/code&gt; and I get a src/main/java/com/ea/foo directory, a TestTest.java and TestTestService.java file in that same directory, a sql file with the table definition (named &lt;code&gt;test_test&lt;/code&gt;), and an empty query file. I also get a perforce changelist (via &lt;a href="http://public.perforce.com/guest/tony_smith/perforce/API/Ruby/main/doc/index.html"&gt;p4Ruby&lt;/a&gt;) with the description filled in and all the new files added.&lt;br /&gt;&lt;br /&gt;Here's the heart of the code. &lt;code&gt;template_to_final&lt;/code&gt; is a hash of template file name locations to the end file destination. The local variables exposed by the call to &lt;code&gt;binding&lt;/code&gt; include the package name, the Java object name, and the SQL-friendly name:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;       template_to_final.keys.each do |key|&lt;br /&gt;           File.open(key) do |file|&lt;br /&gt;               b = binding&lt;br /&gt;               erb = ERB.new(file.read)&lt;br /&gt;               outfile = File.new(template_to_final[key],"w")&lt;br /&gt;               puts "Creating #{outfile.path}"&lt;br /&gt;               outfile.write(erb.result(b))&lt;br /&gt;               outfile.close&lt;br /&gt;           end&lt;br /&gt;       end&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;To give you an idea of what the templates look like, here's some code from the java file templates:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;package &amp;lt;%=java_pkg%&amp;gt;;&lt;br /&gt;&lt;br /&gt;@Entity&lt;br /&gt;@Table(name="&amp;lt;%=java_sql_name%&amp;gt;")&lt;br /&gt;@SequenceGenerator(name="objIdGenerator",sequenceName="object_id_seq",allocationSize=1)&lt;br /&gt;public class &amp;lt;%=java_obj%&amp;gt; {&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;I don't yet go the full Rails route and specify all the properties on the command line, but this takes care of getting the boring parts of business object development out of the way, enforcing consistent naming schemes, and ensuring that the developer doesn't forget to check in some file. I also don't yet modify my config files, but that won't be too tough to add.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-4063338579871396638?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/4063338579871396638/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/08/rails-scaffolding-in-java.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4063338579871396638'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4063338579871396638'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/08/rails-scaffolding-in-java.html' title='Rails Scaffolding In Java'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-8903836184276859712</id><published>2010-08-05T07:22:00.000-07:00</published><updated>2010-08-05T09:09:21.607-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Automation'/><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><title type='text'>Scripting Campfire</title><content type='html'>My team uses &lt;a href="http://campfirenow.com/?source=37signals+home&amp;__utma=1.611799573.1281018183.1281018183.1281018183.1&amp;__utmb=1.4.10.1281018183&amp;__utmc=1&amp;__utmx=-&amp;__utmz=1.1281018183.1.1.utmcsr=(direct)%7cutmccn=(direct)%7cutmcmd=(none)&amp;__utmv=-&amp;__utmk=46212775"&gt;Campfire&lt;/a&gt;, a web-based chat tool from &lt;a href="http://37signals.com"&gt;37signals&lt;/a&gt;, to communicate throughout the day. We've divided it into various rooms, one of which is a Status room where we do a virtual version of the daily stand-up that many teams do: a quick meeting where everyone says what they're working on. (One of our team members is in England, and our hours vary a bit, so a classic stand-up isn't practical.) &lt;br /&gt;&lt;br /&gt;About a month ago, one team member started posting the date in the status room before anyone gave their update. For some reason, Campfire itself doesn't do this reliably, and when you have a room with nothing but status updates, it's not always clear where one day's messages end and another's begin.&lt;br /&gt;&lt;br /&gt;Usually one or two people on the team did this, but I followed suit on a couple of occasions, and of course thought about automating it. We already had a bot account for some very early automation, and Campfire has &lt;a href="http://developer.37signals.com/campfire/"&gt;a good web service&lt;/a&gt;. Insert Tab A into Slot B.&lt;br /&gt;&lt;br /&gt;Here's the relevant Ruby source code, which I have hooked up to a cron job (Note that if you want to script Campfire, I suggest creating a "Bot Test Room" that you can use for experiments without spamming your real rooms):&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;require 'date'&lt;br /&gt;require 'json'&lt;br /&gt;require 'net/http'&lt;br /&gt;require 'net/https'&lt;br /&gt;require 'uri'&lt;br /&gt;&lt;br /&gt;def send_text_to_campfire(text)&lt;br /&gt;    message = {&lt;br /&gt;       :message =&gt; {&lt;br /&gt;           :type =&gt; "TextMessage",&lt;br /&gt;           :body =&gt; text&lt;br /&gt;       }&lt;br /&gt;    }&lt;br /&gt;    send_to_campfire(message)&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def send_to_campfire(message)&lt;br /&gt;    url = URI.parse("https://&amp;lt;your base URL&amp;gt;/room/&amp;lt;your room number&amp;gt;/speak.json")&lt;br /&gt;&lt;br /&gt;    request = Net::HTTP::Post.new(url.path)&lt;br /&gt;    request.basic_auth(&amp;lt;your auth token&amp;gt;,&amp;lt;any password string&amp;gt;)&lt;br /&gt;    request.content_type = 'application/json'&lt;br /&gt;    request.body = message.to_json&lt;br /&gt;    request.content_length = request.body.length&lt;br /&gt;&lt;br /&gt;    http = Net::HTTP.new(url.host,url.port)&lt;br /&gt;    http.use_ssl = true&lt;br /&gt;    response = http.start do |http|&lt;br /&gt;        http.request(request)&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    puts response.body.to_s&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;# construct message&lt;br /&gt;today = Date::today&lt;br /&gt;formattedDateString = sprintf("%02d/%02d/%4d",today.mon,today.mday,today.year)&lt;br /&gt;dateStatusString = "=== Today is #{Date::DAYNAMES[today.wday]}, #{formattedDateString} ==="&lt;br /&gt;&lt;br /&gt;#send status message to campfire&lt;br /&gt;send_text_to_campfire(dateStatusString)&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Filling in the date each day isn't a huge time savings: It will take us a lot of days to recoup the time I spent automating a five-second typing task. But once I figured out the gist of posting to Campfire, I started adding new functionality. Our "Today is ..." status message now includes a few choice statistics &amp;mdash; gleaned from &lt;a href="http://programmingobsession.blogspot.com/2010/07/google-appengine.html"&gt;our telemetry system&lt;/a&gt; &amp;mdash; about gameplay from the previous day. I also wrote a "canary" script that does a health check on our dev server and posts to our general chat room if it seems to be slow.&lt;br /&gt;&lt;br /&gt;Naturally, some of my co-workers have suggested writing an adventure game on top of the API. That may be a bit silly, but it does emphasize a point I often make: You can't really imagine all the possibilities for a technology until you get your hands dirty a bit and play with it.&lt;br /&gt;&lt;br /&gt;This whole experience underlines again why web applications should have APIs. The lack of a date is probably a Campfire bug, but we don't have to wait for them to fix it. And we've added functionality that is only relevant for our team, resulting in something that more closely ties in with our real needs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-8903836184276859712?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/8903836184276859712/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/08/scripting-campfire.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/8903836184276859712'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/8903836184276859712'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/08/scripting-campfire.html' title='Scripting Campfire'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-4237993171767126726</id><published>2010-08-01T10:57:00.000-07:00</published><updated>2010-08-01T11:46:33.408-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Automation'/><category scheme='http://www.blogger.com/atom/ns#' term='AppEngine'/><category scheme='http://www.blogger.com/atom/ns#' term='Philosophy'/><title type='text'>Watch Your Users Use</title><content type='html'>Time and time again, I re-learn this lesson: Nothing makes your system more usable than watching your users use it.&lt;br /&gt;&lt;br /&gt;As I mentioned &lt;a href="http://programmingobsession.blogspot.com/2010/07/google-appengine.html"&gt;in my last post&lt;/a&gt;, I'm building a telemetry system on top of Google AppEngine so that my studio can understand how our games will be played. As I've demoed pieces of it, I've put together web pages and interactive charts and other nice little visualizations. I've made JavaScript libraries to make it easy to build those sorts of things, and I've demoed some of the prettier ones to the stable.&lt;br /&gt;&lt;br /&gt;But I also put in a way to export data as tab-delimited fields. I figured this would be a good last resort if the JavaScript version wasn't quite there yet, or if we hadn't written a specialized view on the data, but I figured we'd favor those.&lt;br /&gt;&lt;br /&gt;Naturally, then, my principal users have all just used the tab-delimited export and not pushed beyond that. In particular, they grab a bunch of tab-delimited data, import it into Excel, and then do manipulations on it there. Though there are macros and various other things, it still seems to take a senior engineer a couple of hours to compile all the data for his weekly reports.&lt;br /&gt;&lt;br /&gt;I know this because I sit with him a fair amount as he explains his process.&lt;br /&gt;&lt;br /&gt;The other day, I was thinking of his process and realized that Python can create Excel spreadsheets. So I could remove one minor barrier by just letting him get the data in Excel format instead of tab-delimited text. Easy.&lt;br /&gt;&lt;br /&gt;But that doesn't buy him much. For his weekly reports, he trims some of the unneccesary columns (my exports grab all the fields and dump them out, including some that are system-level fields), runs some macros to insert formulas to give him percentages, sorts the data, and then copies and pastes that into an email. He does that for about 10-15 reports, in one form or another.&lt;br /&gt;&lt;br /&gt;What if, I thought, I could let him define the format of the spreadsheet the system generates? Then he could say, "give me this data, but put it in this layout." That would shave a big chunk of time from his flow.&lt;br /&gt;&lt;br /&gt;One thing about working with him is that he's not an online engineer. This turns out to be very good, because it makes me think in terms of letting him modify config files instead of modifying server code. (A medium-term goal is building a real interface on top of my system so that anyone in the studio can interact with it, but for now, config files are how we do things.) One of our other online engineers used the system by writing a custom request handler that interacted with the AppEngine datastore directly to generate a customized HTML page. Very neat, but if he were my only user, my system wouldn't be very evolved. I'd just say to someone new, "write a request handler."&lt;br /&gt;&lt;br /&gt;I mentally sketched out what the config file would need to contain, what abilities it would need to give him, and within 45 minutes had a system in place that will let him generate something much closer to the final spreadsheets he needs for his reports.&lt;br /&gt;&lt;br /&gt;A key component of the new feature is the &lt;a href="http://docs.djangoproject.com/en/dev/topics/templates/"&gt;Django templating language&lt;/a&gt; that comes with AppEngine. I actually dislike the language for HTML generation, but for letting users construct simple templates, it's pretty good. One important feature: It can render a template contained in a string, not just one contained in a file. And you can give the renderer a context, filling in some variables that are accessible within the template. In my case, I create a context that contains the current piece of aggregation data (which is a summary object of a large number of events) and the current row number.&lt;br /&gt;&lt;br /&gt;From his perspective, to generate a report about how much damage a creature is doing in our testing, he creates a YAML config file that looks like this:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;header:&lt;br /&gt;    - value: Name&lt;br /&gt;    - value: Avg Damage&lt;br /&gt;    - value: Total Damage&lt;br /&gt;row:&lt;br /&gt;    - value: "{{aggregation.groupByValue}}"&lt;br /&gt;    - value: "{{aggregation.average}}"&lt;br /&gt;    - value: "{{aggregation.sum}}"&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then, when he requests data in Excel format, he can tell it to go through the layout he specifies. Rather than getting a dump of every piece of data in each object, he gets a spreadsheet with just the information he needs. The {{}} is part of the templating system and translates as "spit out the result of this expression."&lt;br /&gt;&lt;br /&gt;Let's say he also wanted to capture the number of times that creature did damage. That's actually available to him in another field in the aggregation, but let's say it wasn't. My system also supports formulas. So he could do this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;header:&lt;br /&gt;    - value: Name&lt;br /&gt;    - value: Count of Attacks&lt;br /&gt;    - value: Avg Damage&lt;br /&gt;    - value: Total Damage&lt;br /&gt;row:&lt;br /&gt;    - value: "{{aggregation.groupByValue}}"&lt;br /&gt;    - formula:"ROUND(D{{row_num}} / C{{row_num}},0)"&lt;br /&gt;    - value: "{{aggregation.average}}"&lt;br /&gt;    - value: "{{aggregation.sum}}"&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;That formula will become, in the output for the first row, &lt;code&gt;ROUND(D1/C1,0)&lt;/code&gt;. &lt;br /&gt;&lt;br /&gt;This is still a long way from user-friendly, but I think it's going to give him back an hour and a half of time each week. And building the system this way means that a user-friendly version just needs to write the format into this config file, and away it will go.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-4237993171767126726?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/4237993171767126726/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/08/watch-your-users-use.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4237993171767126726'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4237993171767126726'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/08/watch-your-users-use.html' title='Watch Your Users Use'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-7155525256628513866</id><published>2010-07-20T18:01:00.000-07:00</published><updated>2010-07-20T20:01:40.106-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='AppEngine'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Google AppEngine</title><content type='html'>Three months ago or so, I had an interesting idea for Google's &lt;a href="http://code.google.com/appengine/"&gt;AppEngine&lt;/a&gt;, an environment for building web applications. I wanted to use it for telemetry.&lt;br /&gt;&lt;br /&gt;Telemetry, literally, means measuring something from afar. (&lt;a href="http://www.merriam-webster.com/dictionary/telemetry"&gt;Merriam-Webster's definition&lt;/a&gt; is somewhat circular if not outright tautological.) In the gaming industry and perhaps others, it means capturing information about how users interact with your system. Think of stats packages for web pages; they're the same thing. You also hear the terms business intelligence and plain old data analysis.&lt;br /&gt;&lt;br /&gt;I want to be clear: This isn't about watching you, in particular, play a game. If you die in a particular spot on a particular level when our newly announced &lt;a href="http://www.darkspore.com"&gt;Darkspore&lt;/a&gt; ships, I don't really care. But if lots of people die in that same spot, it tells you something about the difficulty of that spot, doesn't it?&lt;br /&gt;&lt;br /&gt;I needed something that could take in a large stream of data without hiccuping and allow me to process it and report on it. I went with AppEngine.*&lt;br /&gt;&lt;br /&gt;A big selling point of AppEngine is scalability. Google obviously knows something about running big systems, and they take control of scaling your app. At any given point, I don't know how many servers are running my code: AppEngine starts up new instances as traffic grows and shuts them down when traffic slows. It's not like &lt;a href="http://aws.amazon.com/ec2/"&gt;Amazon.com's EC2&lt;/a&gt;, where you manually say, "I want this many servers. No! Now I want this many!"&lt;br /&gt;&lt;br /&gt;Closely tied to its scalability is the datastore behind AppEngine, which is a proprietary &lt;a href="http://en.wikipedia.org/wiki/Nosql"&gt;NoSQL&lt;/a&gt; technology called &lt;a href="http://en.wikipedia.org/wiki/BigTable"&gt;BigTable&lt;/a&gt;. When you run queries against BigTable, you're using a &lt;a href="http://en.wikipedia.org/wiki/Mapreduce"&gt;MapReduce&lt;/a&gt; algorithm &amp;mdash; the same algorithm that allows Google to index and search the web so quickly. I had a potentially huge set of relatively unconnected data: AppEngine seemed like a good fit.**&lt;br /&gt;&lt;br /&gt;And, for the most part, it has been. &lt;br /&gt;&lt;br /&gt;But the datastore that makes AppEngine so powerful has some quirks that I've had to work around. Keep in mind that it's not a relational database, so it doesn't have the same behaviors. Most notably, it's not very good at aggregating data: sums, averages, whatever. And remember where I said that telemetry is all about aggregation? In the long run, I'm hoping &lt;a href="http://code.google.com/apis/bigquery/"&gt;BigQuery&lt;/a&gt; will make this easy, but in the mean time, I had to build my own aggregation system on top of AppEngine's cron jobs and task queues. The system is pretty neat, including support for a "group by" concept so that I can get views on subsets of the data in a given aggregation, but I'll be happy to let someone else handle that heavy lifting.&lt;br /&gt;&lt;br /&gt;Another issue I've run into is that AppEngine sets hard caps on certain aspects of the system. For instance, any given request can't run too long or it will be terminated. On my own servers, if a webpage for an admin tool takes thirty seconds to load, I'm not too upset. It's an admin tool; I can wait. Google isn't so forgiving, presumably to prevent some process of yours from running amok on their machines. This has led to some early performance tweaking that's probably, in the long run, better to get out of the way now.&lt;br /&gt;&lt;br /&gt;But these have been interesting problems to solve, so I haven't minded them so much as been annoyed by them. We're currently pulling in 100,000 events or so on any given day (as testers play the game and developers work on it), and I've only had to do optimizations on the reporting side and aggregation system. There's a whole other round of work I can do on optimizing writes, but I haven't bothered yet. Granted, 100,000 events isn't that significant, but it's nice to defer some of the deeper tuning until we're a bit closer to shipping. Meanwhile, we've got graphs and charts and spreadsheets with our telemetry data served up in a useful way to different people in the studio.&lt;br /&gt;&lt;br /&gt;It's always hard for me to gauge the ease-of-use of a web system for someone who's not a long-time server engineer, but AppEngine feels like it is very good for either the people who know nothing about web stuff or people who know a lot about it. For middle ground types &amp;mdash; maybe a bit of PHP or other dynamic site system &amp;mdash; who won't be facing scalability issues, I wonder if Rails might be a better choice. &lt;br /&gt;&lt;br /&gt;But I do know that one of our non-online engineers was able to install a new index on AppEngine for a query he wanted without even asking me. I didn't know about it until I happened to see his check-in note. That's pretty powerful. AppEngine supports both Python and Java, but I deliberately went the Python route first, despite not knowing the language at all, because there's more Python knowledge in general in my studio. And that language I didn't know? The code I wrote to take in telemetry events is virtually unchanged from the first day I wrote it. So it's easy to get started on AppEngine, and the cost is free well into the needs of most websites.&lt;br /&gt;&lt;br /&gt;If you're interested in developing on AppEngine, I recommend the yet-to-be-released &lt;a href="http://pragprog.com/titles/mcappe/code-in-the-cloud"&gt;&lt;em&gt;Code in the Cloud&lt;/em&gt;&lt;/a&gt;.The writing in the early drafts is a bit too exuberant, but there's solid information from the get-go, and Pragmatic Programmers delivers polished final products.&lt;br /&gt;&lt;br /&gt;* Yes, yes. I don't trust Google, either, though, as always in blanket statements, the AppEngine team seems to have their hearts in the right place. What's to prevent them from pilfering through our data and gleaning their own information about our users? Well, there is a clause about that in the Terms of Service &amp;mdash; they won't do it &amp;mdash; but I'm not naive. We won't be storing information that anyone else could use to trace back to our users. An ID that makes sense inside EA? Yes, sometimes. Anything that makes any sense anywhere else? Nope. Our legal department was pretty firm on that.&lt;br /&gt;&lt;br /&gt;** What if AppEngine goes down on our big launch day? Am I nervous about tying my company's game to a service that may not be there when I need it? In this case, not really. Let's say AppEngine goes down for a full 24 hours. The whole thing, not just pieces. Such an event would be unprecedented in my experience, but still. So we lose a day of telemetry data. Big deal. People will play the game one day the same way they played it the day before and the day after. Since I'm dealing with aggregates, I can cheerfully ignore missing data.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-7155525256628513866?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/7155525256628513866/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/07/google-appengine.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7155525256628513866'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7155525256628513866'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/07/google-appengine.html' title='Google AppEngine'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-4708117863470178422</id><published>2010-05-26T22:00:00.000-07:00</published><updated>2010-05-26T22:35:29.367-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><title type='text'>What's The Probability Of Two Boys?</title><content type='html'>There is a link going around about &lt;a href="http://www.newscientist.com/article/dn18950-magic-numbers-a-meeting-of-mathemagical-tricksters.html?full=true"&gt;the most recent Gathering For Gardner&lt;/a&gt;. The writer hooks the reader with an intro featuring Gary Foshee (a top-notch designer of "secret box" puzzles, though they're rarely boxes), who poses this question to the crowd of mathematicians, puzzlers, and magicians: "I have two children. One is a boy born on a Tuesday. What is the probability I have two boys?"&lt;br /&gt;&lt;br /&gt;The article then describes the Gathering. (I'm on the invite list, but I've not yet been.) It eventually explains the answer, but these probability questions never make sense. So I wrote a program to illustrate the first oddity, which is that announcing you have two children and one is a boy makes the probability of the other one being a boy only 1/3. Basically, create 10,000 sets, and remove any that are two girls. Now count up the total number of pairs left and the total number of those that are two boys, and you end up with something around one-third.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;num_bbs = 0&lt;br /&gt;total_pairs = 0&lt;br /&gt;&lt;br /&gt;(0...10000).each do |count|&lt;br /&gt;  children = [Kernel.rand(2),Kernel.rand(2)]&lt;br /&gt;  next if children[0] == 1 &amp;&amp; children[1] == 1&lt;br /&gt;&lt;br /&gt;  total_pairs = total_pairs + 1&lt;br /&gt;  num_bbs = num_bbs + 1 if children[0] == 0 &amp;&amp; children[1] == 0&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;puts "#{num_bbs} pairs of boys out of #{total_pairs} valid pairs = #{(num_bbs.to_f/total_pairs.to_f) * 100}"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;It's weird, but it's true.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-4708117863470178422?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/4708117863470178422/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/05/whats-probability-of-two-boys.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4708117863470178422'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4708117863470178422'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/05/whats-probability-of-two-boys.html' title='What&apos;s The Probability Of Two Boys?'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-7443703041190924009</id><published>2010-05-18T21:37:00.000-07:00</published><updated>2010-05-18T22:30:53.655-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Miscellaneous'/><title type='text'>We Rule: Maximize Your Time</title><content type='html'>I've been playing &lt;a href="http://werule.ngmoco.com/"&gt;We Rule&lt;/a&gt; on my iPhone. I have to say, I don't really get the point of it. It seems like the only way to progress is to plant and harvest crops and exchange services with other players. But there are tons of things like trees and banners and what not that don't seem to do anything. Should I place a water tower? Who knows?&lt;br /&gt;&lt;br /&gt;But this is not a game review. When you plant items and harvest them, you get paid some amount of gold depending on the crop. Planting each crop takes some defined amount of money (usually), and each crop requires some defined amount of real-world time to mature.&lt;br /&gt;&lt;br /&gt;So, naturally, I wondered: Which plants are the best investment?&lt;br /&gt;&lt;br /&gt;A simple spreadsheet offers the answer*. I wrote down the profit of the crop and divided that by the number of minutes it would take to grow. I did the same thing for experience points, which allow you to level up. (This list reflects the crops I currently have access to. I'll update as I go.)&lt;br /&gt;&lt;br /&gt;&lt;table rules="all"&gt;&lt;br /&gt;    &lt;tr&gt;&lt;th&gt;Crop&lt;/th&gt;&lt;th&gt;Profit&lt;/th&gt;&lt;th&gt;Profit/Minute&lt;/th&gt;&lt;th&gt;XP/Minute&lt;/th&gt;&lt;/tr&gt;&lt;br /&gt;    &lt;tr&gt;&lt;td&gt;Magic Asparagus&lt;/td&gt;&lt;td&gt;1250&lt;/td&gt;&lt;td&gt;.87&lt;/td&gt;&lt;td&gt;.24&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;    &lt;tr&gt;&lt;td&gt;Potatoes&lt;/td&gt;&lt;td&gt;200&lt;/td&gt;&lt;td&gt;.56&lt;/td&gt;&lt;td&gt;.22&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;    &lt;tr&gt;&lt;td&gt;Pumpkins&lt;/td&gt;&lt;td&gt;160&lt;/td&gt;&lt;td&gt;.89&lt;/td&gt;&lt;td&gt;.36&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;    &lt;tr&gt;&lt;td&gt;Strawberries&lt;/td&gt;&lt;td&gt;120&lt;/td&gt;&lt;td&gt;1.33&lt;/td&gt;&lt;td&gt;.56&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;    &lt;tr&gt;&lt;td&gt;Carrots&lt;/td&gt;&lt;td&gt;260&lt;/td&gt;&lt;td&gt;.36&lt;/td&gt;&lt;td&gt;.15&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;    &lt;tr&gt;&lt;td&gt;Squash&lt;/td&gt;&lt;td&gt;180&lt;/td&gt;&lt;td&gt;.6&lt;/td&gt;&lt;td&gt;.25&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;    &lt;tr&gt;&lt;td&gt;Beans&lt;/td&gt;&lt;td&gt;340&lt;/td&gt;&lt;td&gt;.24&lt;/td&gt;&lt;td&gt;.10&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;    &lt;tr&gt;&lt;td&gt;Onions&lt;/td&gt;&lt;td&gt;100&lt;/td&gt;&lt;td&gt;1.67&lt;/td&gt;&lt;td&gt;.67&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;    &lt;tr&gt;&lt;td&gt;Wheat&lt;/td&gt;&lt;td&gt;20&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;1.60&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;    &lt;tr&gt;&lt;td&gt;Corn&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;6.67&lt;/td&gt;&lt;td&gt;1.33&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;Your definition of a good investment may be different than mine. Clearly the best investment is to plant and harvest corn like a madman every 45 seconds. You have fun with that. Wheat is somewhat more tolerable, with a 5-minute harvest time, but I like to set up a bunch of crops and then go do something else until they're done. Or set up a bunch overnight to have ready the next morning.&lt;br /&gt;&lt;br /&gt;Strawberries and onions are good investments, with onions beating out strawberries for both cash and experience points. Plus, they only take an hour: That's about right for mid-day play. In the crops that take all day, Magic Asparagus is your best best, with beans not even close. Beans also give you one of the worst XP/Minute ratios. Don't plant beans.&lt;br /&gt;&lt;br /&gt;Derive your own fun theories from the list.&lt;br /&gt;&lt;br /&gt;*Yes, this is a programming blog. Spreadsheets &lt;a href="http://www.nsf.gov/discoveries/disc_summ.jsp?cntn_id=100204&amp;org=NSF"&gt;are programs, too&lt;/a&gt;, though they're rarely treated as such.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-7443703041190924009?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/7443703041190924009/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/05/we-rule-maximize-your-time.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7443703041190924009'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7443703041190924009'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/05/we-rule-maximize-your-time.html' title='We Rule: Maximize Your Time'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-8785951352832250255</id><published>2010-05-14T07:41:00.000-07:00</published><updated>2010-05-14T18:59:47.534-07:00</updated><title type='text'>Rails Rules</title><content type='html'>I get now why people love Rails.&lt;br /&gt;&lt;br /&gt;I had a personal project in mind: a web-based application for organizing all the notes you collect while working on an article. Like the index cards you used to use for school reports. I decided to try &lt;a href="http://rubyonrails.org"&gt;Ruby on Rails&lt;/a&gt;, a web application framework built on top of the Ruby language, mostly to see what all the fuss was about. &lt;br /&gt;&lt;br /&gt;Rails is the framework of choice for a wide range of startups because it lets you get up and running quickly. It works by driving home a brutal truth: Your website is not that unique.&lt;br /&gt;&lt;br /&gt;Some sizable percentage of what you need to do for a real-world website is what everyone needs to do for a real-world website: working with databases, adding &lt;a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete"&gt;CRUD&lt;/a&gt; functionality, rendering HTML, mapping handlers to URLs, and so forth. &lt;a href="http://en.battlestarwiki.org/wiki/Sacred_Scrolls"&gt;"All of this has happened before and all of it will happen again."&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Rails acknowledges this and takes care of most of that functionality out of the box. A lot of what it does seems like magic, but really it just imposes naming conventions that will let it do the right thing most of the time. Put your &lt;code&gt;User&lt;/code&gt; objects into a &lt;code&gt;users&lt;/code&gt; table, and you rarely have to write SQL. Organize your URLs as /controller/action/id, and you don't have to map anything manually. Call a controller method &lt;code&gt;create&lt;/code&gt;, and it will automatically be called to handle POST operations. And you don't even have to remember any of this: The scripts that come with Rails generate tons of the code for you, so adding a new business object into the database and HTML-based CRUD pages to manage said objects take two scripts. Two lines at a command prompt, and poof.&lt;br /&gt;&lt;br /&gt;I gave it a whirl and came away impressed. In five hours of flying to the East Coast this weekend, I, who know very little about Rails and something about Ruby, had a functional site for creating, viewing, and editing each of the key object types my application needs. I even added a whole bunch of "it would be nice if it did this" types of features. All on the way to New Hampshire. I wrote minimal amounts of code to do it, too. I have an app that I can use &amp;mdash; indeed, I've started using it for real-world stuff.&lt;br /&gt;&lt;br /&gt;On the way back, I started worrying about the user interface, and that's where I began to flounder a bit. Rails is smooth sailing if you're using all of its tools, but if you want to use something such as &lt;a href="http://jquery.org"&gt;jQuery&lt;/a&gt; for your front-end JavaScript/AJAX, things get tougher. Or perhaps it's that &lt;a href="http://www.pragprog.com/titles/rails3/agile-web-development-with-rails-third-edition"&gt;the book I used&lt;/a&gt; doesn't explain the depths well enough to let me figure it out. A friend of mine says it's easy, and I'm sure it is, but I haven't quite grokked everything I need to do yet.&lt;br /&gt;&lt;br /&gt;But even with this hiccup, Rails is clearly a valuable tool that any web developer should consider.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-8785951352832250255?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/8785951352832250255/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/05/rails-rules.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/8785951352832250255'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/8785951352832250255'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/05/rails-rules.html' title='Rails Rules'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-6019889855853197621</id><published>2010-04-13T12:11:00.000-07:00</published><updated>2010-04-13T14:05:23.654-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Automation'/><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><title type='text'>Ruby + Outlook + Perforce</title><content type='html'>At my work, we have a practice that I've always found curious. Usually, when people check something in, they send out an email to the whole studio with the change list.&lt;br /&gt;&lt;br /&gt;This is, by itself, a fine practice. The curious part to me has always been that Perforce (our version control software) has the ability to send out emails for every check-in. My old boss would subscribe to every check-in in our branch, for instance, so she could keep tabs on what the group was doing. But that's not what we use.&lt;br /&gt;&lt;br /&gt;Over time, I've come to understand the rationale of our redundant workflow. People can attach screenshots (I do work for a game company, after all) and the subject line can provide project categorization and a short description that perforce doesn't know about. Also, culturally speaking, if everyone in your company does this and you don't, people have a harder time figuring out what you do.&lt;br /&gt;&lt;br /&gt;But I still find the workflow annoying. You submit your changes in Perforce. Then you go to the "submitted changelists" pane and double-click on the changelist you just submitted. You select all the text and then copy it (this view has more info in it &amp;mdash; such as the list of files &amp;mdash; than the text you wrote when submitting, so you can't just use that). You alt-tab over to Outlook, open a new message, address it to the studio email address, attach your pithy subject, and then copy and paste in the changelist info. (You also use this moment to attach screenshots, if relevant.) You send the email and then alt-tab back over to Perforce (or your IDE) to do more work.&lt;br /&gt;&lt;br /&gt;I finally decided to automate the process a bit using Ruby to tie together Outlook and Perforce. I set it up to work with my needs, so it may be of limited use to anyone else. For instance, we have a custom of using "mini" to indicate minor, one-liner types of fixes. Otherwise, I tend to use "submit." Also, sometimes I want to aggregate a few recent changelists in one email. That's what the &lt;code&gt;-count&lt;/code&gt; argument does. I also put all the config info (username, password, etc.) into a separate yaml file so that I can distribute the script without sending around my network password. Finally, I specified a sendMode of either Display or Send. Display opens the email for you and lets you customize it. Send just kicks it out the door. The former is useful for screenshots and the like. The &lt;code&gt;sendMode&lt;/code&gt; and &lt;code&gt;project&lt;/code&gt; config file options provide defaults, but they can be overridden. Sometimes I do work in another functional project and need to change the email accordingly.&lt;br /&gt;&lt;br /&gt;You'll need &lt;a href="://p4ruby.rubyforge.org/"&gt;p4Ruby&lt;/a&gt; to make this work. (I think the OLE stuff is built in to the Ruby for Windows installation.) Perforce returns information in sort of an odd way: a changelist will have the list of revisions as one field and the list of files as another. The indexes line up, but it takes a bit more work to get the info you want.&lt;br /&gt;&lt;br /&gt;There are no doubt better ways to do this: I'm still fumbling around with Ruby.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;require 'win32ole'&lt;br /&gt;require 'P4'&lt;br /&gt;require 'yaml'&lt;br /&gt;&lt;br /&gt;is_mini = false&lt;br /&gt;subject = ""&lt;br /&gt;num_cls = 1&lt;br /&gt;&lt;br /&gt;def assert_value(obj,message_on_fail)&lt;br /&gt;   if !obj&lt;br /&gt;      puts message_on_fail&lt;br /&gt;      Kernel.exit&lt;br /&gt;   end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;# load the yaml settings first&lt;br /&gt;assert_value((File.exists? 'p4_email.yaml'),'You must have a p4_email.yaml file in the same directory')&lt;br /&gt;&lt;br /&gt;config = YAML.load_file 'p4_email.yaml'&lt;br /&gt;sendMode = config['sendMode']&lt;br /&gt;project = config['project']&lt;br /&gt;&lt;br /&gt;assert_value(config['p4User'],'No p4User specified!')&lt;br /&gt;assert_value(config['p4Password'], 'No p4Password specified!')&lt;br /&gt;assert_value(config['p4Client'], 'No p4Client specified!')&lt;br /&gt;assert_value(config['p4Host'],'No host specified!')&lt;br /&gt;&lt;br /&gt;# now parse ARGs. In particular, see if the user has overridden config settings&lt;br /&gt;ARGV.each_index do |index|&lt;br /&gt;   if ARGV[index] == '-sendMode'&lt;br /&gt;      sendMode = ARGV[index+1]&lt;br /&gt;      # check value&lt;br /&gt;      assert_value(sendMode == 'Display' || sendMode == 'Send',"Invalid sendMode value: #{sendMode}")&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   if ARGV[index] == '-project'&lt;br /&gt;      project = ARGV[index+1]&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   if ARGV[index] == '-mini'&lt;br /&gt;      is_mini = true&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   if ARGV[index] == '-subject'&lt;br /&gt;       subject = ARGV[index+1]&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   if ARGV[index] == '-count'&lt;br /&gt;      num_cls = ARGV[index+1].to_i&lt;br /&gt;   end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;puts "count: #{num_cls}" &lt;br /&gt;&lt;br /&gt;assert_value(project,'No project specified! Add to p4_email.yaml or use the -project command-line argument')&lt;br /&gt;&lt;br /&gt;# set up p4 connections&lt;br /&gt;p4 = P4.new&lt;br /&gt;p4.client = config['p4Client']&lt;br /&gt;p4.password = config['p4Password']&lt;br /&gt;p4.user = config['p4User']&lt;br /&gt;p4.host= config['p4Host']&lt;br /&gt;&lt;br /&gt;p4.connect&lt;br /&gt;p4.run_login&lt;br /&gt;&lt;br /&gt;# retrieve recent changelists&lt;br /&gt;lists =  p4.run_changes('-u',p4.user,'-m',num_cls, '-s','submitted')&lt;br /&gt;&lt;br /&gt;#get the id&lt;br /&gt;msg_body = ""&lt;br /&gt;(0...(num_cls)).each_with_index do |obj,index| &lt;br /&gt;    cl_num = lists[index]['change']&lt;br /&gt;&lt;br /&gt;    #get the full details for that cl&lt;br /&gt;    cl_full = p4.run_describe(cl_num)[0]&lt;br /&gt;    cl_action_list = cl_full['action']&lt;br /&gt;    cl_rev_list = cl_full['rev']&lt;br /&gt;    msg_body = msg_body + "Change #{cl_num} by #{p4.user}@#{p4.client}\n\n"&lt;br /&gt;    msg_body = msg_body + cl_full['desc'] + "\nAffected files ...\n\n"&lt;br /&gt;    cl_full['depotFile'].each_index do |index|&lt;br /&gt;        msg_body = msg_body + cl_full['depotFile'][index] +\&lt;br /&gt;                   "##{cl_rev_list[index]} " + cl_action_list[index] + "\n"&lt;br /&gt;    end&lt;br /&gt;end&lt;br /&gt;p4.disconnect&lt;br /&gt;&lt;br /&gt;#compose email&lt;br /&gt;outlook = WIN32OLE.new('Outlook.Application')&lt;br /&gt;&lt;br /&gt;message = outlook.CreateItem(0)&lt;br /&gt;submit_type = "submit"&lt;br /&gt;if is_mini&lt;br /&gt;   submit_type = "mini"&lt;br /&gt;end&lt;br /&gt;message.Subject = "p4 [#{project}] #{submit_type}: #{subject}"&lt;br /&gt;message.Body = msg_body&lt;br /&gt;message.To = '[studioemail]'&lt;br /&gt;# todo: should invoke the method by using reflection&lt;br /&gt;if sendMode == 'Display'&lt;br /&gt;   message.Display&lt;br /&gt;elsif sendMode == 'Send'&lt;br /&gt;   message.Send&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-6019889855853197621?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/6019889855853197621/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/04/ruby-outlook-perforce.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6019889855853197621'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6019889855853197621'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/04/ruby-outlook-perforce.html' title='Ruby + Outlook + Perforce'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-6096908177774346917</id><published>2010-04-04T12:46:00.000-07:00</published><updated>2010-04-04T13:57:56.209-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><title type='text'>Thoughts On Core Data</title><content type='html'>I started a new iPhone app, and I decided to use the Core Data framework. &lt;br /&gt;&lt;br /&gt;For my first app, I built an object wrapper around calls to sqlite, the embedded database built in to the iPhone frameworks. Core Data didn't exist, so everyone had to roll their own solution to this problem. I thought about just using my original solution again &amp;mdash; it's well tested, it's a few tweaks from total reusability, and I know SQL well &amp;mdash; but my iPhone programming is mostly about learning new technologies, so I gave Core Data a try.&lt;br /&gt;&lt;br /&gt;Core Data is basically an &lt;a href="http://en.wikipedia.org/wiki/Object-relational_mapping"&gt;ORM&lt;/a&gt; system. I've used a number of these over the years; I've even written some, including, in a minor way, the sqlite wrapper I mentioned above. All the ones I've seen abstract away the notion of a "database" so that the bulk of the system just sees objects without knowing their origin. &lt;br /&gt;&lt;br /&gt;Here are some of my initial thoughts on Core Data.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;   &lt;li&gt;Core Data abstracts the database away so much that you can't actually get to it. I recognize that Core Data can run on top of any number of storage solutions, but I feel like if I know it's running over a database, I should be able to manipulate the database myself. Bulk updates of database info &amp;mdash; versus loading each object and modifying it &amp;mdash; are just one scenario where that would be useful.&lt;/li&gt;&lt;br /&gt;   &lt;li&gt;Objects managed by Core Data have to extend a single base class. This isn't a huge problem for my model, but it does mean you use up the one inheritance you have in Objective-C. Java has the same limitation, and most of its ORM solutions don't require you to extend a class, which gives you more flexibility in the long run.&lt;/li&gt;&lt;br /&gt;    &lt;li&gt;Migrating a model should not be an "advanced" topic. One minor change to a model, and you have to nuke the data for your app, which is a bother when you're actually using it. Yes, &lt;a href="http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/CoreDataVersioning/Introduction/Introduction.html"&gt;there are a range of ways to accomplish your goal&lt;/a&gt;. But in my first iPhone app, I just wrote a few lines of SQL and had them run against the database at startup: &lt;a href="http://programmingobsession.blogspot.com/2009/04/database-updates-redux.html"&gt;Migration to new models was a snap&lt;/a&gt;.&lt;/li&gt;&lt;br /&gt;   &lt;li&gt;The &lt;code&gt;&lt;a href="http://developer.apple.com/iphone/library/documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html"&gt;NSFetchedResultsController&lt;/a&gt;&lt;/code&gt; is a delight to use. With a few short lines of code, you have a model object you can use to drive table views of data.&lt;/li&gt;&lt;br /&gt;   &lt;li&gt;Maybe I haven't read up on it enough, but when Core Data is running against a database, I'd like to see explain plans for its queries and be able to check its index usage.&lt;/li&gt;&lt;br /&gt;   &lt;li&gt;Running arbitrary queries is extremely verbose, again because of the inability to run SQL directly. I wanted the ability to display a unique list of existing non-null values for an object's property in my app so that a user could either enter a new one or select an existing one. In SQL, that would be something like &lt;code&gt;SELECT DISTINCT property_column FROM object_table WHERE property_column IS NOT NULL ORDER BY property_column&lt;/code&gt;. The Core Data version of this is:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;    NSFetchRequest *request = [[NSFetchRequest alloc] init];&lt;br /&gt;    [request retain];&lt;br /&gt;    NSEntityDescription *entity = [NSEntityDescription entityForName:@"CallSlip" inManagedObjectContext:[self managedObjectContext]];&lt;br /&gt;    [request setEntity:entity];&lt;br /&gt;    [request setResultType:NSDictionaryResultType];&lt;br /&gt;    NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:researchAreaField];&lt;br /&gt;    &lt;br /&gt;    NSExpressionDescription *expressionDescription = [[[NSExpressionDescription alloc] init] autorelease];&lt;br /&gt;    [expressionDescription setName:researchAreasKey];&lt;br /&gt;    [expressionDescription setExpression:keyPathExpression];&lt;br /&gt;    &lt;br /&gt;    [request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];&lt;br /&gt;    &lt;br /&gt;    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ != nil", researchAreaField];&lt;br /&gt;    [request setPredicate:predicate];&lt;br /&gt;    &lt;br /&gt;    [request setReturnsDistinctResults:YES];&lt;br /&gt;    &lt;br /&gt;    NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:researchAreaField ascending:YES selector: @selector(caseInsensitiveCompare:)] autorelease];&lt;br /&gt;    NSArray *descriptors = [NSArray arrayWithObject:descriptor];&lt;br /&gt;    [request setSortDescriptors:descriptors];&lt;br /&gt;    &lt;br /&gt;    NSError *error = nil;&lt;br /&gt;    NSArray *results = [[self managedObjectContext] executeFetchRequest:request error:&amp;error];&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That version isn't exactly shorter.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;Compared to other, similar frameworks, I'd rank Core Data as decent. I imagine it's scalable enough for a client application, where you probably don't have to worry about anything larger than 50,000 records. And, if you don't know SQL, it's probably better than just dumping an object tree into a file. But if you know databases, you're likely to find it frustrating as often as you find it useful.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-6096908177774346917?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/6096908177774346917/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/04/thoughts-on-core-data.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6096908177774346917'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6096908177774346917'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/04/thoughts-on-core-data.html' title='Thoughts On Core Data'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-4381754179156408600</id><published>2010-03-17T09:35:00.000-07:00</published><updated>2010-03-17T14:06:30.887-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><title type='text'>Dynamic XML Schema Elements With JAXB</title><content type='html'>I solved an interesting problem the other day at work, and I wanted to write about it here, mostly so I don't forget how I did it.&lt;br /&gt;&lt;br /&gt;I can't talk about what I'm doing at work at the moment, but I came up with a scenario from a different industry that presents some of the same problems.&lt;br /&gt;&lt;br /&gt;Let's say you're building a web service for a stock trading desk. You generate a variety of reports in XML and JSON so that you can build a robust, AJAX-y front end solution.&lt;br /&gt;&lt;br /&gt;Imagine that the head of the desk wants a report that gives a summary of all the stocks traded that day, complete with volume bought and volume sold. The desk trades different stocks each day, of course, and there are a vast number of valid ticker symbols that could appear, with more coming online as companies go public and with some disappearing as companies get delisted.&lt;br /&gt;&lt;br /&gt;You might end up with an XML structure that looks something like this:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;report&amp;gt;&lt;br /&gt;    &amp;lt;tradedStocks&amp;gt;&lt;br /&gt;        &amp;lt;stock&amp;gt;&lt;br /&gt;            &amp;lt;symbol&amp;gt;AAPL&amp;lt;/symbol&amp;gt;&lt;br /&gt;            &amp;lt;bought&amp;gt;100&amp;lt;/bought&amp;gt;&lt;br /&gt;            &amp;lt;sold&amp;gt;100&amp;lt;/sold&amp;gt;&lt;br /&gt;        &amp;lt;/stock&amp;gt;&lt;br /&gt;        &amp;hellip;&lt;br /&gt;    &amp;lt;/tradedStocks&amp;gt;&lt;br /&gt;&amp;lt;/report&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;With a corresponding JSON structure like this:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;{"report":{"tradedStocks":[&lt;br /&gt;    {"symbol":"AAPL","bought":"100","sold":"100"},&lt;br /&gt;    &amp;hellip;]}}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;So far, so good. Now imagine that the head of the desk wants to treat AAPL differently. Instead of being mixed in with the other stocks traded that day, it should be at the head of the list and printed in green.&lt;br /&gt;&lt;br /&gt;When you code this special-case logic on the front end, it will probably look something like this (in JavaScript):&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;    function findSymbol(symbolName) {&lt;br /&gt;        for (stock in report.tradedStocks) {&lt;br /&gt;            if(stock.symbol == symbolName) {&lt;br /&gt;               return stock;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        return undefined;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    aapl = findSymbol("AAPL");&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Even wrapped in a function, that's a bit cumbersome. Plus, if the desk has traded hundreds of stocks, that iteration can be time-consuming, especially if the head of the desk wants to call out stocks that the desk &lt;em&gt;shouldn't&lt;/em&gt; be trading: The code goes through the entire list of stocks only to return &lt;code&gt;undefined&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;It would be easier and more readable to be able to do something like this:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;aapl = report.tradedStocks.AAPL&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;But that would necessitate an XML structure in which the element name &lt;code&gt;stock&lt;/code&gt; was replaced by the element name &lt;code&gt;AAPL&lt;/code&gt;. Which would in turn mean that the elements allowed under &lt;code&gt;tradedStocks&lt;/code&gt; were drawn from a very large list of ever-changing element names. In essence, subelements under &lt;code&gt;tradedStocks&lt;/code&gt; could literally be anything.&lt;br /&gt;&lt;br /&gt;XML doesn't really allow for that. It assumes you have a well-defined structure. And tools like JAXB build on that.&lt;br /&gt;&lt;br /&gt;You could put the special-case logic on the server-side, of course. Grab the AAPL data from the list of traded stocks, and make a sub-element of &lt;code&gt;report&lt;/code&gt; be &lt;code&gt;AAPL&lt;/code&gt; with further subelements showing the data. That would repeat data, which may not be the end of the world, but what about the next time the head of the desk wanted special-case logic? More server-side custom logic.&lt;br /&gt;&lt;br /&gt;Here's how I solved the problem.&lt;br /&gt;&lt;br /&gt;First, I changed the logic in our XML formatter. My view layer is isolated from the rest of the application, and within it it shuffles the model to different formatters depending on the format that was requested: xml goes to the XML formatter, json goes to the JSON formatter, and so forth.&lt;br /&gt;&lt;br /&gt;The first incarnation of our XML formatter did the obvious thing: It just used the JAXB engine to spit out the XML based on the JAXB annotations. But I made it act more like our JSON formatter, which &lt;a href="http://programmingobsession.blogspot.com/2010/01/jaxb-json.html"&gt;receives events from a parser that analyzes the JAXB annotations&lt;/a&gt; and constructs the output based on those events.&lt;br /&gt;&lt;br /&gt;Why switch? Because I wanted custom annotations. By making our XML formatter act as an event listener too, I could interject events based on annotations that JAXB doesn't know about. The new XML formatter (and the JSON formatter) wouldn't notice anything odd about the data, because it would just be another event.&lt;br /&gt;&lt;br /&gt;Next I created a &lt;code&gt;FlattenableMap&lt;/code&gt; annotation, which our JAXB parser spies and interprets as "take this map, and for each key-value pair, fire an appropriate event." To use the above example, there would be a Map in the report object that would key stock ticker symbols to stock objects. In that case, our parser would say "I'm starting a complex object whose name is 'AAPL'." All the infrastructure would then fire appropriately, and you'd end up with&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;report&amp;gt;&lt;br /&gt;    &amp;lt;tradedStocks&amp;gt;&lt;br /&gt;        &amp;lt;AAPL&amp;gt;&lt;br /&gt;           &amp;lt;bought&amp;gt;100&amp;lt;/bought&amp;gt;&lt;br /&gt;           &amp;lt;sold&amp;gt;100&amp;lt;/sold&amp;gt;&lt;br /&gt;        &amp;lt;/AAPL&amp;gt;&lt;br /&gt;    &amp;lt;/tradedStocks&amp;gt;&lt;br /&gt;&amp;lt;/report&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;On the front-end side, a JavaScript programmer merely writes:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;   report.tradedStocks.AAPL&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This implementation also means that no matter what stocks show up in the report, they'll be pushed out to the client (assuming the stocks get loaded into the Map). There's no need to maintain the code to add "allowed" stocks. So it's automatically maintainable based on the real data in front of it, even if that data changes (which, in my case, it certainly will).&lt;br /&gt;&lt;br /&gt;You could fairly point out that this means we're not using valid XML. You could certainly not construct a DTD or schema for this logic. But the reality is that eventually, we probably will have needs defined enough to allow us to construct the equivalent of &lt;code&gt;report.AAPL&lt;/code&gt;, at which point we can have a more rigid schema. In effect, our schema is still so much in flux that every week or so creates a different schema. My code keeps up with those changes, even without me doing any new work.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-4381754179156408600?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/4381754179156408600/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/03/dynamic-xml-schema-elements-with-jaxb.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4381754179156408600'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4381754179156408600'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/03/dynamic-xml-schema-elements-with-jaxb.html' title='Dynamic XML Schema Elements With JAXB'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-2724145358837838247</id><published>2010-03-04T20:43:00.000-08:00</published><updated>2010-03-04T21:18:49.214-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><title type='text'>Highest-Scoring Word In WordCrasher</title><content type='html'>I've become a fan of &lt;a href="http://www.wordcrasher.com/game/about.html"&gt;WordCrasher&lt;/a&gt; on the iPhone. Think of it as &amp;lt;insert your favorite word game&amp;gt; meets Tetris: You tap on bubbles, which are falling from the top, to make words and clear them from the screen.&lt;br /&gt;&lt;br /&gt;I've unlocked most of the achievements, but there are two secret achievements that have escaped me. I'm convinced that one of them is finding the highest-scoring word in the dictionary. The in-game view of the leaderboards, as of this writing, shows just four people who have managed to find a 2,300-point word, the highest recorded score.&lt;br /&gt;&lt;br /&gt;What is the highest-scoring word? Well, I don't know. But I wrote a Ruby script to make an educated guess. I assumed Kevin Ng, the developer, used the Official Scrabble Dictionary as his dictionary (though his game omits at least &lt;em&gt;ort&lt;/em&gt; and &lt;em&gt;gams&lt;/em&gt;). &lt;br /&gt;&lt;br /&gt;So I wrote this script, which takes a path to a dictionary file as a command-line argument:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$letterScores = {&lt;br /&gt;  'a' =&amp;gt; 10,&lt;br /&gt;  'b' =&amp;gt; 20,&lt;br /&gt;  'c' =&amp;gt; 20,&lt;br /&gt;  'd' =&amp;gt; 20,&lt;br /&gt;  'e' =&amp;gt; 10,&lt;br /&gt;  'f' =&amp;gt; 30,&lt;br /&gt;  'g' =&amp;gt; 30,&lt;br /&gt;  'h' =&amp;gt; 20,&lt;br /&gt;  'i' =&amp;gt; 10,&lt;br /&gt;  'j' =&amp;gt; 50,&lt;br /&gt;  'k' =&amp;gt; 20,&lt;br /&gt;  'l' =&amp;gt; 10,&lt;br /&gt;  'm' =&amp;gt; 30,&lt;br /&gt;  'n' =&amp;gt; 10,&lt;br /&gt;  'o' =&amp;gt; 10,&lt;br /&gt;  'p' =&amp;gt; 20,&lt;br /&gt;  'q' =&amp;gt; 80,&lt;br /&gt;  'r' =&amp;gt; 10,&lt;br /&gt;  's' =&amp;gt; 10,&lt;br /&gt;  't' =&amp;gt; 10,&lt;br /&gt;  'u' =&amp;gt; 10,&lt;br /&gt;  'v' =&amp;gt; 30,&lt;br /&gt;  'w' =&amp;gt; 30,&lt;br /&gt;  'x' =&amp;gt; 50,&lt;br /&gt;  'y' =&amp;gt; 30,&lt;br /&gt;  'z' =&amp;gt; 50 }&lt;br /&gt;  &lt;br /&gt;def calc_word_score(word) &lt;br /&gt;   sum = 0&lt;br /&gt;   word.split(//).each do |char|&lt;br /&gt;      sum = sum + $letterScores[char] if $letterScores[char]&lt;br /&gt;   end&lt;br /&gt;   sum * word.length&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;File.open(ARGV[0]) do |file|&lt;br /&gt;    file.each_line do |word|&lt;br /&gt;        score = calc_word_score(word.downcase)&lt;br /&gt;        isBest = (score == 2300)&lt;br /&gt;        puts "#{score} #{word}" if isBest&lt;br /&gt;    end&lt;br /&gt;end    &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;I put in 2,300 as a score because that's what people have achieved. However, in the basic Scrabble dictionary, there's no word that scores exactly 2,300; there's one word that scores 2,350: &lt;em&gt;zyzzyvas&lt;/em&gt;.  So instead of the official Scrabble dictionary, I used &lt;a href="http://puzzlers.org/dokuwiki/doku.php?id=solving:wordlists:about:enable_readme"&gt;the Enable wordlist&lt;/a&gt; (both wordlists are available &lt;a href="http://puzzlers.org/dokuwiki/doku.php?id=solving:wordlists:about:start"&gt;from the National Puzzlers' League&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;With the Enable list, I found two words that scored exactly 2,300: &lt;em&gt;showbizzy&lt;/em&gt; and &lt;em&gt;whizzbang&lt;/em&gt;. I have yet to get a board where I can spell any of these, but I'm going to be trying all of them as soon as I can.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-2724145358837838247?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/2724145358837838247/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/03/highest-scoring-word-in-wordcrasher.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/2724145358837838247'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/2724145358837838247'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/03/highest-scoring-word-in-wordcrasher.html' title='Highest-Scoring Word In WordCrasher'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-3498972595465017847</id><published>2010-02-06T14:38:00.000-08:00</published><updated>2010-02-07T09:15:19.674-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Geek Humor'/><title type='text'>Tail Recursion</title><content type='html'>The annoying thing about being a writer who has focused a lot on learning his craft is that I now have a constant editorial chatter now when I'm reading. Typos, awkward sentences, factual problems. They all crop up and prevent me from just taking in what I'm reading.&lt;br /&gt;&lt;br /&gt;I was reading through a &lt;a href="http://www.artima.com/shop/programming_in_scala"&gt;Scala book&lt;/a&gt; the other day, and I noticed this blurb in a section about tail recursion.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;(If you don’t fully understand tail recursion yet, see Section 8.9).&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;8.10 Conclusion&lt;/h3&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;The editor in my brain pounced on this end sentence, which cross-references to the same section the reader has just finished.&lt;br /&gt;&lt;br /&gt;It took a beat before the programmer side of my brain woke up and noticed the joke.&lt;br /&gt;&lt;br /&gt;Tail recursion is when the last instruction in a method is a call to the same method. To take an example from the book, &lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;def boom(x: Int): Int =&lt;br /&gt;         if (x == 0) throw new Exception("boom!")&lt;br /&gt;        else boom(x - 1) + 1&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;In this specific case, &lt;code&gt;boom&lt;/code&gt; calls itself on the last line of the method. That's tail recursion.&lt;br /&gt;&lt;br /&gt;And the last sentence in that section is a perfect example.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-3498972595465017847?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/3498972595465017847/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/02/tail-recursion.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/3498972595465017847'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/3498972595465017847'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/02/tail-recursion.html' title='Tail Recursion'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-7957854069509155473</id><published>2010-02-01T07:23:00.000-08:00</published><updated>2010-02-01T07:26:23.366-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Neat'/><title type='text'>Print To URL Via Smartphone</title><content type='html'>According to &lt;a href="http://feeds.mashable.com/~r/Mashable/~3/3PI1dk-EVWc/"&gt;this Mashable post&lt;/a&gt;, Microsoft has unveiled a new "tagging" system that would let print publication have a smartphone-readable link so that readers could visit a webpage referenced in an article by pointing their smartphones at it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-7957854069509155473?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/7957854069509155473/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/02/print-to-url-via-smartphone.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7957854069509155473'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7957854069509155473'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/02/print-to-url-via-smartphone.html' title='Print To URL Via Smartphone'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-6852754295679141175</id><published>2010-01-30T12:37:00.000-08:00</published><updated>2010-01-30T14:35:54.440-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><title type='text'>JAXB -&gt; JSON</title><content type='html'>I'm working on a new project at work, and it has a by-now-standard RESTful API web service layer. And, of course, like all such layers, it needs to support output in XML or JSON.&lt;br /&gt;&lt;br /&gt;Supporting XML is easy in Java, thanks to a technology called JAXB. Among its many capabilities is one which lets you annotate Java objects and generate an XML document from those annotations.&lt;br /&gt;&lt;br /&gt;For instance, you could write an object with the following:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;@XmlElement(name="name")&lt;br /&gt;private String name = "Joe";&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Pass that to the JAXB marshaller, and you'd get this XML:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;&amp;hellip;&lt;br /&gt;&amp;lt;name&amp;gt;Joe&amp;lt;/name&amp;gt;&lt;br /&gt;&amp;hellip;&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And the reverse is true: Make JAXB parse the XML, and you'd end up with an object whose &lt;code&gt;name&lt;/code&gt; instance variable was set to Joe.&lt;br /&gt;&lt;br /&gt;So our XML version was done in no time. But JSON support is a less-entrenched technology, and thus there's no elegant built-in solution. My initial idea was to just use one of the JSON libraries out there and have it construct a JSON object from the XML document generated by JAXB: Dump the XML into a buffer, convert it, and print that text to the HTTP response output stream. That works (though it's not very efficient), except in this particular case:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;things&amp;gt;&lt;br /&gt;    &amp;lt;thing&amp;gt;&lt;br /&gt;         &amp;lt;name&amp;gt;Joe&amp;lt;/name&amp;gt;&lt;br /&gt;    &amp;lt;/thing&amp;gt;&lt;br /&gt;&amp;lt;things&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Your schema might define &lt;code&gt;things&lt;/code&gt; as a collection with zero or more items, but the XML-to-JSON converter doesn't see the schema, and so it creates a &lt;code&gt;things&lt;/code&gt; &lt;em&gt;object&lt;/em&gt; instead of a &lt;code&gt;things&lt;/code&gt; &lt;em&gt;collection&lt;/em&gt;. Once there are two items in the collection, everything works fine. (It's possible that if we had an actual schema doc somewhere, the converter could know about it. But since the annotations define the output, we don't have an xsd file around at the moment. )&lt;br /&gt;&lt;br /&gt;I looked at various options for mapping objects, but most felt like duplicate work: I'd have to maintain a JSON mapping in sync with the XML mapping, a setup that's guaranteed to get messed up in some subtle way someday. What I really wanted to do was use the JAXB annotations as the definition for the JSON output.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://wikis.sun.com/display/Jersey/JSON+support+in+JAXB"&gt;Jersey&lt;/a&gt; looks like it's trying to accomplish the same thing, but it also seemed (when I first looked at it) to be not yet ready for prime time.&lt;br /&gt;&lt;br /&gt;So I came up with my own solution. While I can't post the code, I can give you a good sense of how it worked. &lt;br /&gt;&lt;br /&gt;I created a concrete object,&lt;code&gt;JAXBBridgeParser&lt;/code&gt;. I also created an interface, &lt;code&gt;JAXBBridgeParserListener&lt;/code&gt;. You throw a JAXB annotated object and an implementation of &lt;code&gt;JAXBBridgeParserListener&lt;/code&gt; at the parser, and it uses reflection to find the annotated fields and methods in that object. For each annotated field/method, it calls some appropriate method on the listener.&lt;br /&gt;&lt;br /&gt;In addition to the ubiquitous &lt;code&gt;startParsing&lt;/code&gt; and &lt;code&gt;finishParsing&lt;/code&gt; messages, the parser fires special-purpose messages at the listener. The easiest scenario is what I call the "simple type" field or method. A String, a Java primitive, a Date, etc. In that case, the parser says to the listener, "Here's the name of this field and here's the value." In the JSON scenario, this translates to a key-value pair.&lt;br /&gt;&lt;br /&gt;Next up is what I call the "complex type" field or method. This is a value that is itself an annotated object. In that scenario, the parser first tells the listener that it's beginning a complex object with a given name; then it recurses into a &lt;code&gt;processObject&lt;/code&gt; method with that new object. That will in turn trigger its own "simple type" or "complex type" messages. When it comes out of the recursive call, the parser tells the listener that it's done with the complex object of the given name. This corresponds to a JSON key-value pair where the value is an object.&lt;br /&gt;&lt;br /&gt;Finally, I have to worry about collections. These could contain simple types or complex types. When the parser sees an annotated field or method that is a collection, it tells the listener that it's starting a collection via the &lt;code&gt;beginCollection&lt;/code&gt; method in the interface. For each item in the collection, it sends a message to the listener telling it that it's processing an object inside a collection. There are separate methods for simple types and complex types in collections. When it's done with the collection, it tells the listener that it's finished. In JSON, that corresponds to a list that might look like this: &lt;code&gt;["a","b",{c: "c",d:"d"}]&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;The end result works like a charm: My JSON objects line up perfectly with our XML documents, and I don't have to do anything to get them there. The JSON listener is about 20 lines of code. Any object that can be converted to XML can also be converted to JSON simply by passing it through the system. (A custom Spring &lt;code&gt;ViewResolver&lt;/code&gt; figures out the right converter to use based on the request, so anything that can be served up as XML also automatically gets a JSON version.)&lt;br /&gt;&lt;br /&gt;And I can support new formats pretty easily as they rise in prominence in the web world. I've thought about wiring up an HTML formatter that would give default browser versions of the data. HTML is a little trickier because I'd want some of the items &amp;mdash; object IDs, for instance &amp;mdash; to be attributes and some of the items &amp;mdash; names, for instance &amp;mdash; to be page content. But it should be doable. I've also thought of wiring up YAML support, which should be brain-dead simple, just because I can.&lt;br /&gt;&lt;br /&gt;My particular scenario is pretty simple: We don't use some of the deeper features of JAXB, so I don't have to worry about handling them. I do, however, support &lt;code&gt;@XmlJavaTypeAdapter&lt;/code&gt;, running the referenced adapter on a field before the parser sends the value to the listener. And my version has the downside that I, and not some larger open-source community, have to support and extend it. Still, it was a pleasant little exercise, and it's working well.&lt;br /&gt;&lt;br /&gt;If you go this route, I encourage you not only to have lots of unit tests to catch subtle edge cases, but also to set up a more behavior-focused test. In my case, I made a test listener that simply counts the number of messages it gets from the parser. I set up an object with a fixed set of annotations, and then passed it and the listener to the parser. My "unit test" then checks the counts for each message in the listener.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-6852754295679141175?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/6852754295679141175/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/01/jaxb-json.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6852754295679141175'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6852754295679141175'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/01/jaxb-json.html' title='JAXB -&gt; JSON'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-315451698146519119</id><published>2010-01-23T19:32:00.000-08:00</published><updated>2010-01-24T08:58:42.721-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='Philosophy'/><title type='text'>Polyglot Programming</title><content type='html'>I recently read through &lt;a href="http://pragprog.com/titles/twa/thoughtworks-anthology"&gt;&lt;em&gt;The ThoughtWorks Anthology&lt;/em&gt;&lt;/a&gt;, a collection of essays by Big Thinkers in the realm of systems design.  The essays were largely interesting, but one in particular resonated with me: Polyglot Programming. The author made a compelling case for using the Java Virtual Machine &amp;mdash; a robust, mature, well-tested infrastructure &amp;mdash; as a platform in which any number of languages can co-exist.&lt;br /&gt;&lt;br /&gt;Java's a good language, of course, but it's not good at everything. Why not mix in other languages that can run in the virtual machine but offer strengths in the face of Java's weaknesses, asked the author. I've toyed with this idea before, especially with adding Scala's potential for highly concurrent code, but the essay lit a new fire in me.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;I came up with a way to try out the concept. We have a bunch of queries in our service layers, and Java blows when it comes to formatting long strings. Without the ability to have one string span multiple lines, you end up with something like this:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;String query = "SELECT table_a.*,table_b.* FROM table_a,table_b,table_c " +&lt;br /&gt;                          "WHERE table_a.some_column = table_b.some_column " +&lt;br /&gt;                          "AND table_b.some_column = table_c.some_column " +&lt;br /&gt;                          "AND table_c.id = :idValue";&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Easy reading, right? Not only is it annoying to read, it's error prone. I almost always forget a space in one of these long strings, causing SQL exceptions that don't show up until runtime.&lt;br /&gt;&lt;br /&gt;Ruby, like many other scripting languages, allows for a "here document" which basically says, "Treat this text following double less-than signs as a double quote and just pull in everything after it until you see the same text again." In Ruby, you might write the query above as follows:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;query = &amp;lt;&amp;lt;QUERY&lt;br /&gt;SELECT&lt;br /&gt;     table_a.*,&lt;br /&gt;     table_b.*&lt;br /&gt;FROM&lt;br /&gt;     table_a,&lt;br /&gt;     table_b,&lt;br /&gt;     table_c&lt;br /&gt;WHERE&lt;br /&gt;     table_a.some_column = table_b.some_column&lt;br /&gt;     AND table_b.some_column = table_c.some_column&lt;br /&gt;     AND table_c.id = :idValue&lt;br /&gt;QUERY&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Which is more readable. I admit this is not a monumental problem, but it did offer an opportunity.&lt;br /&gt;&lt;br /&gt;Enter &lt;a href="http://jruby.org/"&gt;JRuby&lt;/a&gt;. JRuby is a Ruby interpreter written in Java. Curiously, it now outperforms the C-based interpreter in lots of benchmarks, which is not only a testament to the maturity of the JVM but to the dedicated open-source team that have devoted themselves to improving JRuby. JRuby's main benefit is that you can access the sweeping Java API from within your Ruby scripts, but you can also &lt;a href="http://kenai.com/projects/jruby/pages/RedBridge"&gt;invoke Ruby scripts from your Java code&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I made a new class called &lt;code&gt;QueryContainer&lt;/code&gt; that would serve as a &lt;a href="http://en.wikipedia.org/wiki/Facade_pattern"&gt;facade&lt;/a&gt; for managing the Ruby invocations and giving Hibernate &lt;code&gt;Query&lt;/code&gt; objects back to the service layer. No other layer in the code would need to know about invoking Ruby: &lt;code&gt;QueryContainer&lt;/code&gt; would translate the scripts into objects useful elsewhere in the system. Inside each Ruby script, I made a class to act as a namespace (because I opted for a singleton of the Ruby interpreter instead of multiple copies), and then inside each class defined hash literals that looked something like this:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;QUERY_1 = {&lt;br /&gt;:type=&gt;AppConstants::SQL,&lt;br /&gt;:query =&gt; &amp;lt;&amp;lt;QUERY&lt;br /&gt;SELECT&lt;br /&gt;     table_a.*,&lt;br /&gt;     table_b.*&lt;br /&gt;FROM&lt;br /&gt;     table_a,&lt;br /&gt;     table_b,&lt;br /&gt;     table_c&lt;br /&gt;WHERE&lt;br /&gt;     table_a.some_column = table_b.some_column&lt;br /&gt;     AND table_b.some_column = table_c.some_column&lt;br /&gt;     AND table_c.id = :idValue&lt;br /&gt;QUERY&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;What's that &lt;code&gt;AppConstants::SQL&lt;/code&gt; thing? &lt;code&gt;AppConstants&lt;/code&gt; is a Java class in our system that has some globally useful constants. Because it's JRuby, I can use constants from my Java classes. We have two query languages in our system: normal SQL and Hibernate's abridged SQL. &lt;code&gt;QueryContainer&lt;/code&gt; needs to know which query language it is because Hibernate defines a &lt;code&gt;createQuery&lt;/code&gt; method for HSQL and a &lt;code&gt;createSQLQuery&lt;/code&gt; method for SQL.&lt;br /&gt;&lt;br /&gt;But it gets more complicated. If you have a SQL query that returns everything you need to construct a Hibernate object, you need to tell Hibernate what kind of object it is. (You don't need to do this for HSQL.) I added an &lt;code&gt;entityClass&lt;/code&gt; key to the SQL hash literals, and had it reference a Java class object (&lt;code&gt;.java_class&lt;/code&gt; when you're in JRuby, since &lt;code&gt;.class&lt;/code&gt; has meaning in the Ruby world. In other words:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;QUERY_1 = {&lt;br /&gt;:type =&gt; AppConstants::SQL,&lt;br /&gt;:entityClass =&gt; BusinessObject.java_class,&lt;br /&gt;&amp;hellip;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Here's the final flow. Some method in the service layer wants to run a query. It calls a method in the base class called &lt;code&gt;getQueryForKey&lt;/code&gt;, passing in the query key it wants. That base class method calls a similar method on a &lt;code&gt;QueryContainer&lt;/code&gt; instance variable held by the base class. &lt;code&gt;QueryContainer&lt;/code&gt; was initialized with the Ruby script that will act as a resource, and it reaches into it to find the keys in the hash literal with the same name as the key that's been moving through the chain. e.g.,: &lt;code&gt;QuerySet1::QUERY_1[:query]&lt;/code&gt;. If it's an HSQL query (&lt;code&gt;QuerySet1::QUERY_1[:type]&lt;/code&gt;), &lt;code&gt;QueryContainer&lt;/code&gt; just constructs a regular &lt;code&gt;Query&lt;/code&gt; object. If it's a SQL query, &lt;code&gt;QueryContainer&lt;/code&gt; constructs a &lt;code&gt;SQLQuery&lt;/code&gt; object and calls &lt;code&gt;addEntity&lt;/code&gt; on it, passing in the Java class from the &lt;code&gt;:entityClass&lt;/code&gt; key of the hash literal.&lt;br /&gt;&lt;br /&gt;So how does it work? Well, on the one hand, it accomplishes what I wanted. My queries have been factored out into new files, and they've been re-produced in a format that's easier to read and less error-prone. The entire rest of the system is ignorant of their source. It makes the case for adding languages that have strengths (in this case, the relatively minor advantage of string literals that can span multiple lines) to a deploy.&lt;br /&gt;&lt;br /&gt;But on the other hand, JRuby seems to have added a sizable chunk of memory to our app. Shortly after I put in this system, our dev server started running into &lt;code&gt;OutOfMemory&lt;/code&gt; errors on a regular basis, a process that I've contained somewhat by disabling some other systems. And this is with a singleton of the interpreter. I've found little information about this, and so I'm wondering if JRuby is the way to go. I haven't hooked up a profiler yet to determine the real source, but it's the only thing that's changed.&lt;br /&gt;&lt;br /&gt;I've started looking at &lt;a href="http://groovy.codehaus.org/"&gt;Groovy&lt;/a&gt; as an alternate. At least if I go that route, only &lt;code&gt;QueryContainer&lt;/code&gt; needs to change.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-315451698146519119?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/315451698146519119/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/01/polyglot-programming.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/315451698146519119'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/315451698146519119'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/01/polyglot-programming.html' title='Polyglot Programming'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-5259589508488939064</id><published>2010-01-20T09:51:00.000-08:00</published><updated>2010-01-20T10:21:46.657-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Projects'/><title type='text'>Menu For Hope Source</title><content type='html'>For the past few years, I've run the raffle program that generates the winners for Menu For Hope, an annual fundraising event started by &lt;a href="http://www.chezpim.com"&gt;Pim&lt;/a&gt; that incorporates food bloggers from around the world and raises tons of money for hunger relief.&lt;br /&gt;&lt;br /&gt;It's been a couple of years since I first posted the source code, so I thought I'd post the current version. It hasn't changed much: I made the parsing a bit smarter and added support for giving people tickets even if they asked for fewer tickets than they bought (I used to skip those as invalid). Looking at it now, I see a number of ways it could be cleaner and better architected (can you say regex?), but it continues to work. If you see any bugs, feel free to mention them!&lt;br /&gt;&lt;br /&gt;Keep in mind that I don't run the program blindly: I run it and then look over every line of output to make sure the program is behaving the way I expect. If it's not, or I see an error, I usually end up fixing some minor data issue (People often think O is the same as 0, for some reason, or they write UW01x2 UW02x3 instead of 2xUW01, 3xUW02. My program handles the latter but not the former because it attaches the 2 to the second string in the field instead of the first.)&lt;br /&gt;&lt;br /&gt;Here's the main class.&lt;br /&gt;&lt;code&gt;&lt;br /&gt;  public class MFHRaffle {&lt;br /&gt;  &lt;br /&gt;  &lt;br /&gt;   public static void main(String[] args) {&lt;br /&gt;     List&lt;String&gt; prizeCodes = new ArrayList&lt;String&gt;();&lt;br /&gt;     try {&lt;br /&gt;  RandomAccessFile prizeFile = &lt;br /&gt;      new RandomAccessFile("prizeFile.txt","r");&lt;br /&gt;  String curPrize;&lt;br /&gt;  while ((curPrize = prizeFile.readLine()) != null) {&lt;br /&gt;      prizeCodes.add(curPrize.trim());&lt;br /&gt;      System.out.println("Added prize code: " + curPrize);&lt;br /&gt;  }&lt;br /&gt;     } catch (IOException ioe) {&lt;br /&gt;  System.err.println("Error reading prize code file");&lt;br /&gt;  ioe.printStackTrace();&lt;br /&gt;     }&lt;br /&gt;    // divide args into buckets&lt;br /&gt;    // there's only one input arg (file) so we can size list in advance&lt;br /&gt;    List commandArgs = new ArrayList(args.length - 1);&lt;br /&gt;    Map params = new HashMap();&lt;br /&gt;    String filename = null;&lt;br /&gt;    for (int i = 0; i &lt; args.length; i++) {&lt;br /&gt;     if (args[i].startsWith("-")) {&lt;br /&gt;      commandArgs.add(args[i].substring(1,args[i].length()));&lt;br /&gt;      if (args[i].startsWith("-oneprize") ) {&lt;br /&gt;       params.put("oneprize",args[i+1]);&lt;br /&gt;      }&lt;br /&gt;     } else {&lt;br /&gt;      filename = args[i];&lt;br /&gt;     }&lt;br /&gt;    }&lt;br /&gt;  System.out.println("Using file: " + filename);&lt;br /&gt;  &lt;br /&gt;  if (commandArgs.contains("testrandomdraw")) {&lt;br /&gt;   testRandomDraw();&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  boolean debug = true;&lt;br /&gt;  if (commandArgs.contains("debug")) {&lt;br /&gt;   debug = true;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  MFHDataParser parser = null;&lt;br /&gt;  if (commandArgs.contains("csv")) {&lt;br /&gt;   parser = new CSVDataParser();&lt;br /&gt;     } else {&lt;br /&gt;      parser = new ExcelDataParser();&lt;br /&gt;     } &lt;br /&gt;     parser.setDebug(debug);&lt;br /&gt;            parser.setValidPrizes(prizeCodes);&lt;br /&gt;     &lt;br /&gt;     Map&amp;lt;String,List&amp;lt;String&amp;gt;&amp;gt; entries = parser.extractData(filename);&lt;br /&gt;     Map&amp;lt;String,Integer&amp;gt; prizeCounts = new HashMap&amp;lt;String,Integer&amp;gt;();&lt;br /&gt;     Map&amp;lt;String,String&amp;gt; prizeToWinner = new HashMap&amp;lt;String,String&amp;gt;();&lt;br /&gt;     &lt;br /&gt;     //produce a sorted list&lt;br /&gt;     List&amp;lt;String&amp;gt; sortedPrizes = new ArrayList&amp;lt;String&amp;gt;(entries.keySet());&lt;br /&gt;     Collections.sort(sortedPrizes);&lt;br /&gt;     &lt;br /&gt;     System.out.println("*******************************");&lt;br /&gt;     if (!commandArgs.contains("oneprize")) {&lt;br /&gt;   // for every entry in map, throw list to randomDraw&lt;br /&gt;   for (Iterator&amp;lt;String&amp;gt; prizeIt = sortedPrizes.iterator();&lt;br /&gt;    prizeIt.hasNext();) {&lt;br /&gt;     // drumroll please...&lt;br /&gt;     String prize = prizeIt.next();       &lt;br /&gt;     List&amp;lt;String&amp;gt; bidders= entries.get(prize);&lt;br /&gt;     prizeCounts.put(prize,new Integer(bidders.size() * 10));&lt;br /&gt;     &lt;br /&gt;     String winnerEmail = randomDraw(bidders);&lt;br /&gt;     prizeToWinner.put(prize,winnerEmail);&lt;br /&gt;   }&lt;br /&gt;  } else {&lt;br /&gt;   String prizeCode = (String)params.get("oneprize");&lt;br /&gt;      String winnerEmail = randomDraw(entries.get(prizeCode));&lt;br /&gt;      prizeToWinner.put(prizeCode,winnerEmail);&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;   &lt;br /&gt;  // tab-delimited output for fatemeh&lt;br /&gt;  System.out.println("********** TEXT ****************");&lt;br /&gt;  for (Iterator&amp;lt;String&amp;gt; prizeIt = sortedPrizes.iterator();&lt;br /&gt;   prizeIt.hasNext();) {&lt;br /&gt;    String prize = prizeIt.next();&lt;br /&gt;    System.out.println(prize+"\t$"+prizeCounts.get(prize)+"\t"+&lt;br /&gt;     parser.getNameForEmail(prizeToWinner.get(prize)) +"\t"+&lt;br /&gt;     prizeToWinner.get(prize));&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  // html markup for brett&lt;br /&gt;  System.out.println("*********************************");&lt;br /&gt;  System.out.println("********** HTML *****************");&lt;br /&gt;  System.out.println("&amp;lt;table rules=\"rows\" &amp;gt;");&lt;br /&gt;  for (Iterator&amp;lt;String&amp;gt; prizeIt = sortedPrizes.iterator();&lt;br /&gt;   prizeIt.hasNext();) {&lt;br /&gt;    String prize = prizeIt.next();&lt;br /&gt;    System.out.println("&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;"+prize+"&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;$"+&lt;br /&gt;        prizeCounts.get(prize) + "&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;" +&lt;br /&gt;        parser.getNameForEmail(prizeToWinner.get(prize)) +&lt;br /&gt;         "&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;" + prizeToWinner.get(prize) +&lt;br /&gt;          "&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;");&lt;br /&gt;  }&lt;br /&gt;  System.out.println("&amp;lt;/table&amp;gt;");&lt;br /&gt;&lt;br /&gt;           &lt;br /&gt; } &lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Here's the base class for parsers:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;public abstract class MFHDataParser {&lt;br /&gt;     &lt;br /&gt;    private boolean debug=false;&lt;br /&gt;    &lt;br /&gt;    private char[] delims = {',',' ','.',';'};&lt;br /&gt;    &lt;br /&gt;    private Map&amp;lt;String,String&amp;gt; emailToName = new HashMap&amp;lt;String,String&amp;gt;();&lt;br /&gt;    &lt;br /&gt;    private List&amp;lt;String&amp;gt; validPrizes = new ArrayList&amp;lt;String&amp;gt;();&lt;br /&gt;    &lt;br /&gt;    public void setValidPrizes(List&amp;lt;String&amp;gt; prizeList) {&lt;br /&gt;     this.validPrizes = prizeList;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    protected void mapEmailToName(String email, String name) {&lt;br /&gt;     this.emailToName.put(email, name);&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public String getNameForEmail(String email) {&lt;br /&gt;     return this.emailToName.get(email);&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public abstract Map&amp;lt;String,List&amp;lt;String&amp;gt;&amp;gt; extractData(String filename);&lt;br /&gt;    &lt;br /&gt; /** Do our darndest to figure out what prizes a donator has mentioned on&lt;br /&gt;  *  a given line. Note: Be sure to complain if we don't recognize a prize.&lt;br /&gt;  *  &lt;br /&gt;  */&lt;br /&gt;    protected List&amp;lt;String&amp;gt; extractPrizes(String prizes, int amount) &lt;br /&gt;     throws NoPrizeFoundException {&lt;br /&gt;    &lt;br /&gt;     String ucPrizes = prizes.toUpperCase().trim(); //for consistency's sake&lt;br /&gt;     if (isDebug()) {&lt;br /&gt;      System.out.println("Incoming prize string " + ucPrizes);&lt;br /&gt;     }&lt;br /&gt;    &lt;br /&gt;     // basic strategy&lt;br /&gt;     // find two numbers followed by a delim (, space, ., eol, ;)&lt;br /&gt;     // back up and find two letters before it&lt;br /&gt;     // then back up (not into an earlier code) and find a #&lt;br /&gt;     // can't use java's regex abilities because i need to divide into larger chunks&lt;br /&gt;     &lt;br /&gt;     // put that many copies into the list&lt;br /&gt;     // verify that size of list = donation /10. bark if not&lt;br /&gt;     // List = one of each real raffle ticket (5xUW03 -&amp;gt; 5 entries in List)&lt;br /&gt;     &lt;br /&gt;     List&amp;lt;String&amp;gt; prizeList = new ArrayList&amp;lt;String&amp;gt;();&lt;br /&gt;     List&amp;lt;Integer&amp;gt; prizeCounts = new ArrayList&amp;lt;Integer&amp;gt;();&lt;br /&gt;     &lt;br /&gt;     if (ucPrizes.length() &amp;lt; 4 ) {&lt;br /&gt;      throw new NoPrizeFoundException(ucPrizes);&lt;br /&gt;     } else if (ucPrizes.length() == 4) {&lt;br /&gt;      // exact count. easy case, but verify that it's legit&lt;br /&gt;      &lt;br /&gt;      String prizeCode = findPrizeCodeInTextBlock(ucPrizes); &lt;br /&gt;      if (!validPrizes.contains(prizeCode)) {&lt;br /&gt;       System.out.println("INVALID PRIZE CODE: " + prizeCode);&lt;br /&gt;         }&lt;br /&gt;      for (int i =1;i&amp;lt;= amount/10; i++) {&lt;br /&gt;       prizeList.add(prizeCode);&lt;br /&gt;      }&lt;br /&gt;     } else {&lt;br /&gt;      // in this case we need to walk through the list, divided it into&lt;br /&gt;      // chunks, and find the prize code in each&lt;br /&gt;      int chunkStart = 0;&lt;br /&gt;      for (int i = 0; i &amp;lt; ucPrizes.length();i++) {&lt;br /&gt;       if (i == ucPrizes.length() - 1 || &lt;br /&gt;        isDelim(ucPrizes.charAt(i))) {&lt;br /&gt;        String curChunk = null;&lt;br /&gt;        if (isDelim(ucPrizes.charAt(i))) {&lt;br /&gt;          curChunk = ucPrizes.substring(chunkStart,i);&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        if (i == ucPrizes.length() - 1) {&lt;br /&gt;         curChunk = ucPrizes.substring(chunkStart,i+1);&lt;br /&gt;        }&lt;br /&gt;        if (curChunk.length() &amp;lt; 4) {&lt;br /&gt;         continue;&lt;br /&gt;        }&lt;br /&gt;        String prizeCode = findPrizeCodeInTextBlock(curChunk);&lt;br /&gt;        if (prizeCode.equals("")) {&lt;br /&gt;         continue; // not in this text block&lt;br /&gt;        } else if (!this.validPrizes.contains(prizeCode)) {&lt;br /&gt;         System.out.println("INVALID PRIZE CODE: " + prizeCode);&lt;br /&gt;           }&lt;br /&gt;        &lt;br /&gt;        &lt;br /&gt;           prizeList.add(prizeCode);&lt;br /&gt;        int prizeCount = new Integer(&lt;br /&gt;         findPrizeQuantityInTextBlock(curChunk,prizeCode));&lt;br /&gt;        if (prizeCount == -1) {&lt;br /&gt;         prizeCounts.add(new Integer(1));&lt;br /&gt;        } else {&lt;br /&gt;               prizeCounts.add(new Integer(prizeCount));&lt;br /&gt;           }&lt;br /&gt;        &lt;br /&gt;        chunkStart = i + 1;&lt;br /&gt;       }&lt;br /&gt;      }&lt;br /&gt;     }&lt;br /&gt;      &lt;br /&gt;  // expand prize list as needed&lt;br /&gt;  if (prizeList.size() == amount /10) {&lt;br /&gt;   return prizeList; // if there are as many prizes as the amount&lt;br /&gt;         // would suggest, do one ticket each&lt;br /&gt;  } else if (prizeList.size() == 1) {&lt;br /&gt;   // create an expanded list that has one entry for each ticket&lt;br /&gt;   List&amp;lt;String&amp;gt; newPrizeList = new ArrayList&amp;lt;String&amp;gt;(amount/10);&lt;br /&gt;   for (int i = 0;i &amp;lt; (amount /10); i++) {&lt;br /&gt;     newPrizeList.add(prizeList.get(0));&lt;br /&gt;   }&lt;br /&gt;   return newPrizeList;&lt;br /&gt;  } else {&lt;br /&gt;   // we have a mix of amounts and quantities&lt;br /&gt;   List&amp;lt;String&amp;gt; newPrizeList = new ArrayList&amp;lt;String&amp;gt;(amount/10);&lt;br /&gt;   for (int i = 0; i &amp;lt; prizeList.size();i++) {&lt;br /&gt;    Integer count = prizeCounts.get(i);&lt;br /&gt;    for (int j = 0; j &amp;lt; count.intValue(); j++) {&lt;br /&gt;     newPrizeList.add(prizeList.get(i));&lt;br /&gt;    }&lt;br /&gt;   }&lt;br /&gt;   return newPrizeList;&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;    &lt;br /&gt;    protected int parseAmount(String amtString) {&lt;br /&gt;        if (amtString.trim().equals("")) {&lt;br /&gt;            return 0;&lt;br /&gt;        }&lt;br /&gt;     return (int)(Double.parseDouble(amtString));&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /** Takes a guess at the prize quantity in a given text block */&lt;br /&gt;    private int findPrizeQuantityInTextBlock(String chunk, String prizeCode) {&lt;br /&gt;     // make a spaced version so we can look for UC 01 as well as UC01&lt;br /&gt;     // walk over the string looking for numbers, skipping the prize code&lt;br /&gt;     String spacedPrizeCode = prizeCode.substring(0,2) + " " +&lt;br /&gt;                           prizeCode.substring(2,4);&lt;br /&gt;                &lt;br /&gt;        String newChunk = chunk.replace(prizeCode,"");&lt;br /&gt;        newChunk = newChunk.replace(spacedPrizeCode,"");&lt;br /&gt;                           &lt;br /&gt;     for (int i = 0; i &amp;lt; newChunk.length(); i++) {&lt;br /&gt;      if (Character.isDigit(newChunk.charAt(i))) {&lt;br /&gt;       if (i &amp;lt; newChunk.length() - 1 &amp;&amp;&lt;br /&gt;        Character.isDigit(newChunk.charAt(i+1))) {&lt;br /&gt;        // two-digit quantity&lt;br /&gt;        char[] digits = {newChunk.charAt(i),newChunk.charAt(i+1)};&lt;br /&gt;        return Integer.parseInt(new String(digits));&lt;br /&gt;       } else {&lt;br /&gt;           return Integer.parseInt(newChunk.substring(i,i+1));&lt;br /&gt;       }&lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  // one last check. some bidders wrote "TWO" instead of 2&lt;br /&gt;  if (newChunk.indexOf("TWO") != -1) {&lt;br /&gt;   return 2;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  return -1;&lt;br /&gt;    }&lt;br /&gt;        &lt;br /&gt; /** Returns the offset of something that looks like a prize code. */&lt;br /&gt;    private String findPrizeCodeInTextBlock(String chunk)  {&lt;br /&gt;     &lt;br /&gt;     // look for 2 letters followed by 2 numbers =&amp;gt; prize code&lt;br /&gt;     for (int i = 3; i &amp;lt; chunk.length(); i++) {&lt;br /&gt;      if (Character.isDigit(chunk.charAt(i)) &amp;&amp; &lt;br /&gt;       Character.isDigit(chunk.charAt(i - 1))) {&lt;br /&gt;       if (chunk.charAt(i-2) == ' ') {&lt;br /&gt;        if (Character.isLetter(chunk.charAt(i-3)) &amp;&amp;&lt;br /&gt;         Character.isLetter(chunk.charAt(i-4))) {&lt;br /&gt;            return chunk.substring(i-4,i-2) +&lt;br /&gt;                 chunk.substring(i-1,i+1);&lt;br /&gt;        }&lt;br /&gt;       } else {&lt;br /&gt;        if (Character.isLetter(chunk.charAt(i-2)) &amp;&amp;&lt;br /&gt;         Character.isLetter(chunk.charAt(i-3))) {&lt;br /&gt;            return chunk.substring(i-3,i+1);&lt;br /&gt;        }&lt;br /&gt;         }&lt;br /&gt;      }&lt;br /&gt;        }&lt;br /&gt;        return "";&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    protected boolean isDebug() {&lt;br /&gt;     return this.debug;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public void setDebug(boolean debug) {&lt;br /&gt;     this.debug = debug;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    private boolean isDelim(char c) {&lt;br /&gt;     for (int i = 0; i &amp;lt; this.delims.length; i++) {&lt;br /&gt;      if (c == delims[i]) {&lt;br /&gt;       return true;&lt;br /&gt;      }&lt;br /&gt;     }&lt;br /&gt;     return false;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And here's the Excel-parsing subclass of Data Parser (you can see that it's got some logic that should be in DataParser, but in truth we've always used the Excel format, so it hasn't been an issue.&lt;br /&gt;&lt;code&gt;&lt;br /&gt;public class ExcelDataParser extends MFHDataParser {&lt;br /&gt;&lt;br /&gt;    public Map&amp;lt;String,List&amp;lt;String&amp;gt;&amp;gt; extractData(String filename) {&lt;br /&gt;     Map&amp;lt;String,List&amp;lt;String&amp;gt;&amp;gt; retVal = new HashMap&amp;lt;String,List&amp;lt;String&amp;gt;&amp;gt;();&lt;br /&gt;     try {&lt;br /&gt;      Workbook wkbk = Workbook.getWorkbook(new File(filename));&lt;br /&gt;      Sheet sheet = wkbk.getSheet(0);&lt;br /&gt;      for (int i = 0; i &amp;lt; sheet.getRows(); i++) {&lt;br /&gt;    if (i == 0) { continue; }// skip headers&lt;br /&gt;    String name = sheet.getCell(0,i).getContents();&lt;br /&gt;    String email = sheet.getCell(1,i).getContents();&lt;br /&gt;    String date = sheet.getCell(2,i).getContents();&lt;br /&gt;    if (isDebug()) {&lt;br /&gt;     System.out.println("amount: " + sheet.getCell(3,i).getContents());&lt;br /&gt;    }&lt;br /&gt;    int amt = parseAmount(sheet.getCell(3,i).getContents());&lt;br /&gt;    String comment = sheet.getCell(4,i).getContents();&lt;br /&gt;    if (email == null || email.trim().equals("")) {&lt;br /&gt;     throw new IllegalArgumentException("No email found at line " + i);&lt;br /&gt;    }&lt;br /&gt;    mapEmailToName(email,name);&lt;br /&gt;&lt;br /&gt;    &lt;br /&gt;    if (comment == null || comment.trim().length() == 0) {&lt;br /&gt;     System.out.println("No comment on line " + (i+1));&lt;br /&gt;     System.out.println("");&lt;br /&gt;     continue;&lt;br /&gt;    }&lt;br /&gt;    List&amp;lt;String&amp;gt; prizes = extractPrizes(comment,amt);&lt;br /&gt;    if (isDebug()) {&lt;br /&gt;     System.out.print( "prizes for line " + (i+1) + " ");&lt;br /&gt;     for (Iterator&amp;lt;String&amp;gt; prizeIt = prizes.iterator();&lt;br /&gt;          prizeIt.hasNext();) {&lt;br /&gt;          System.out.print(prizeIt.next() + " " );&lt;br /&gt;     }&lt;br /&gt;     System.out.print("\n");&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    if (prizes.size() != amt / 10) {&lt;br /&gt;     System.out.println("Line " + (i+1) + &lt;br /&gt;     " does not have the right number of prizes for the amt");&lt;br /&gt;     System.out.println("");&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    // compress the lists down to MFHPair, which includes an email&lt;br /&gt;    // and a count. Insert into map, keyed by prize code&lt;br /&gt;    Collections.sort(prizes); // make sure they're in order&lt;br /&gt;    String curPrize = "";&lt;br /&gt;    int curCount = 0;&lt;br /&gt;    for (Iterator&amp;lt;String&amp;gt; prizeIt = prizes.iterator();&lt;br /&gt;         prizeIt.hasNext();) {&lt;br /&gt;         String prizeFromList = prizeIt.next();&lt;br /&gt;         List&amp;lt;String&amp;gt; bidders = null;&lt;br /&gt;         if (retVal.containsKey(prizeFromList)) {&lt;br /&gt;          bidders = retVal.get(prizeFromList);&lt;br /&gt;         } else {&lt;br /&gt;          bidders = new ArrayList&amp;lt;String&amp;gt;();&lt;br /&gt;          retVal.put(prizeFromList,bidders);&lt;br /&gt;         }&lt;br /&gt;         bidders.add(email);&lt;br /&gt;     }&lt;br /&gt;     System.out.println("");&lt;br /&gt;     }&lt;br /&gt;     return retVal;&lt;br /&gt;  }&lt;br /&gt;  catch (Exception e) {&lt;br /&gt;   System.err.println("There was a problem: " + e);&lt;br /&gt;   e.printStackTrace();&lt;br /&gt;   System.exit(1);&lt;br /&gt;  }&lt;br /&gt;     return null;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-5259589508488939064?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/5259589508488939064/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/01/menu-for-hope-source.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/5259589508488939064'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/5259589508488939064'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/01/menu-for-hope-source.html' title='Menu For Hope Source'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-1586253246951521758</id><published>2010-01-16T09:38:00.000-08:00</published><updated>2010-01-16T09:38:31.333-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><title type='text'>buildr</title><content type='html'>A co-worker of mine seems to subscribe to some mailing list where he hears about every framework and tool that ever comes into existence. And he seems to bring up each one as a potentially neat and useful addition to our tool belt at work. I tease him about it, but he is good at finding interesting things.&lt;br /&gt;&lt;br /&gt;About one-third of his suggestions strike me as worth investigation. About one-third of those make it past a quick docs read and into my "we should incorporate this" mental bucket.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://buildr.apache.org"&gt;buildr&lt;/a&gt; is the most recent tool to run this gauntlet and end up in our source tree. buildr is a build tool that runs on top of rake, Ruby's equivalent of make, but with a Java app mindset. The mentality behind buildr seems to be, "Just because we're building Java packages doesn't mean we need to have a tool written in Java with some clunky XML config system. Maybe a scripting language would be a better choice. And, since Java build tools aren't a new frontier, let's solve the problems that are commonplace in them while we're at it."&lt;br /&gt;&lt;br /&gt;Java's standard build tools have notable flaws. The original authors of &lt;a href="http://ant.apache.org"&gt;ant&lt;/a&gt; decided that ant should not be a programming language. The problem is that you often want some flow-of-control/variable setting for specialized tasks in your build, and you certainly want some ability to modularize. Because ant doesn't give you lots of ways to do these things (though there are add-ons that fill this gap), you end up with verbose XML files with an annoying amount of repetition. (If you're in this boat, you might want to check out the "Refactoring Ant Build Files" essay in &lt;a href="http://pragprog.com/titles/twa/thoughtworks-anthology"&gt;&lt;em&gt;The ThoughtWorks Anthology&lt;/em&gt;&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;&lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; tried to fix some of ant's problems with a convention-over-configuration mindset: Set up a directory structure in a standard way, and maven's plugins will do the right thing with a minimum of configuration. Deploying a normal war required about three lines of XML. Maven also introduced a notion of dependencies and repositories. In an ant system, you find a library you need, download its jar into your source tree, and check it in. In the Maven universe, you just put an entry in your build file, and Maven goes and fetches the jar from a network-based repository.&lt;br /&gt;&lt;br /&gt;But should you need to stray from the convention, Maven gets in your way. Want to use the ant technique of downloading a library for some reason (like, for instance, it's an Oracle driver that can't be published from Maven repositories because of licensing)? You can set up an internal repository, but it's a fair amount of work for a developer on a small team with a lot of other things to do. You can customize Maven (and ant, for that matter) with plugins written in Java, but that always seems clunky.&lt;br /&gt;&lt;br /&gt;Buildr tries to solve these problems. It does all the dependency stuff that Maven does, but it also allows you to construct an artifact object (the term for a built resource) around an arbitrary file. Setting up buildr at work, I faced the problem that the Oracle drivers I need aren't in the global Maven repository anymore. So I downloaded the jar, stuck it in a lib directory, and just created an artifact reference to that file. (One caveat: buildr doesn't yet do the "transitive dependency tracking" that Maven does. In Maven, if you need a library, the system will fetch it and all the libraries that that library depends on. buildr requires you to list every single dependency. &lt;br /&gt;&lt;br /&gt;Buildr also supports hierarchical/environment-specific properties as a feature. You often need to have properties files that contain different values for different environments (database connection URLs, for instance), and you usually have to roll your own solution. buildr acknowledges this common need and &lt;a href="http://buildr.apache.org/settings_profiles.html#environments"&gt;just addresses it&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;And if you need to do something specialized, you can write Ruby code inline. No building a Java library that conforms to some API and then configuring the build tool to use it.&lt;br /&gt;&lt;br /&gt;I've got local builds working in buildr, but I need to convert our automated build systems to use it. Once those are in place, I plan to move forward with buildr as our sole build tool.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-1586253246951521758?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/1586253246951521758/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/01/buildr.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/1586253246951521758'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/1586253246951521758'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/01/buildr.html' title='buildr'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-3634624251034531408</id><published>2010-01-10T12:04:00.000-08:00</published><updated>2010-01-10T12:14:23.671-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='When I Have Time'/><title type='text'>Programmers And Statistics</title><content type='html'>A recent post on Slashdot links to &lt;a href="http://zedshaw.com/essays/programmer_stats.html"&gt;this (strongly worded) article&lt;/a&gt; urging programmers to learn some statistics.&lt;br /&gt;&lt;br /&gt;I am certainly not one of the programmers he mentions who pretends to a deep knowledge of statistics. I took it in college, got an A, and forgot just about everything except calculating a mean and a median. I can define a standard deviation, with a bit of hand-waving.&lt;br /&gt;&lt;br /&gt;But his article is inspiring, especially because I want our team to own the load testing of our application. I (somewhat) recently bought &lt;a href="http://www.powells.com/biblio/2-9780596510497-0"&gt;&lt;em&gt;Statistics In A Nutshell&lt;/em&gt;&lt;/a&gt;, so maybe I'll pick it up again at some point.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-3634624251034531408?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/3634624251034531408/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2010/01/programmers-and-statistics.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/3634624251034531408'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/3634624251034531408'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2010/01/programmers-and-statistics.html' title='Programmers And Statistics'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-4577034579475199439</id><published>2009-12-17T07:29:00.001-08:00</published><updated>2009-12-17T07:52:57.131-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><title type='text'>Loadtesting As Functional Testing</title><content type='html'>I'm a fan of automated testing that exercises code and checks its results.&lt;br /&gt;&lt;br /&gt;But I don't believe in striving for 100 percent coverage. Should I really spend time writing unit tests that exercise our database interactions given that Hibernate, our ORM layer, is heavily tested and community-vetted? I create mock versions of the service layer we put on top of our ORM to allow testing of the service clients, but I don't (usually) write tests to exercise the services themselves.&lt;br /&gt;&lt;br /&gt;This works out fine. Except when I need to do a big refactor in the service layer, as I did yesterday. I moved common functionality to a base class and used &lt;a href="http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf"&gt;Java Generics [pdf]&lt;/a&gt; to define methods in the base class that would, in the context of the child class, compile as if they returned the type appropriate to that child class.&lt;br /&gt;&lt;br /&gt;How could I ensure that I hadn't broken anything before checking in the code? I thought of our load testing scripts, which I've been diligent about maintaining. They do a full &lt;a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete"&gt;CRUD cycle&lt;/a&gt; on a variety of object types. &lt;br /&gt;&lt;br /&gt;I didn't need to test load, but this would exercise virtually all the code I had changed. I brought them up, set the thread count to 1, pointed them at my machine, and pressed go. Sure enough, they uncovered a few bugs. I fixed them, re-ran the scripts, and they all passed.&lt;br /&gt;&lt;br /&gt;Automated testing is good. No matter what form it's in.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-4577034579475199439?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/4577034579475199439/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/12/loadtesting-as-functional-testing.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4577034579475199439'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4577034579475199439'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/12/loadtesting-as-functional-testing.html' title='Loadtesting As Functional Testing'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-5422224054277496885</id><published>2009-12-11T19:01:00.000-08:00</published><updated>2009-12-12T09:18:50.836-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Curmudgeon'/><category scheme='http://www.blogger.com/atom/ns#' term='When I Have Time'/><title type='text'>Facebook's Puzzles</title><content type='html'>A co-worker of mine pointed me to &lt;a href="http://www.facebook.com/careers/puzzles.php"&gt;Facebook's Puzzle Page&lt;/a&gt;, a suite of challenging puzzles. The puzzles exist to find skilled programmers that Facebook might like to hire by letting those programmers sift themselves out, rather than taxing Facebook employees' time with trying to find wheat in the chaff of job applicants who come through normal channels.&lt;br /&gt;&lt;br /&gt;But even if you're not interested in working at Facebook, they're a nice set of programming challenges. Especially if you're &lt;a href="http://programmingobsession.blogspot.com/2009/12/learning-ruby.html"&gt;learning a new language&lt;/a&gt; and want some meaty exercises.&lt;br /&gt;&lt;br /&gt;Even as someone who has challenged interviewees with logic puzzles &amp;mdash; a standard practice for software companies &amp;mdash; I find these types of puzzles to be interesting but not necessarily useful in finding good hires. Yes, it helps you find the bright folks who can figure out clever algorithms.&lt;br /&gt;&lt;br /&gt;But those aren't necessarily the people who make good software engineers. Software engineering is about delivering quality code on schedule and within the budget. It's about writing code that other people will be able to work with and maintain, even years later. It's about debugging, but it's also about exposing bugs as early as possible. It's about understanding trade-offs, risks, and benefits. I spend a very small percentage of my time sussing out clever algorithms: I spend a very large percentage figuring out how to deliver scalable, high-quality features in a cost-effective way.&lt;br /&gt;&lt;br /&gt;Being a good puzzle solver doesn't preclude those skills, of course, but it doesn't point to them, either. Puzzles test that you're bright in one way; they don't tell your employer if you can actually do your job.&lt;br /&gt;&lt;br /&gt;On the other hand, Facebook is no doubt swamped with would-be employees, so these puzzles offer a way for someone to prove that they can be internally motivated (the only effective form of motivation) and self-starting. And they do test for one sort of programming intelligence. And, finally, I haven't yet figured out a way to test for the kind of programmer I look for, other than interviewing people.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-5422224054277496885?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/5422224054277496885/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/12/facebooks-puzzles.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/5422224054277496885'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/5422224054277496885'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/12/facebooks-puzzles.html' title='Facebook&apos;s Puzzles'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-5331084285475211074</id><published>2009-12-09T22:27:00.000-08:00</published><updated>2009-12-11T07:59:38.461-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><title type='text'>Learning Ruby</title><content type='html'>Recently, I decided to learn Ruby, the programming language &lt;em&gt;du jour&lt;/em&gt; of Internet startups. &lt;br /&gt;&lt;br /&gt;The main reason was an essay in &lt;a href="http://pragprog.com/titles/twa/thoughtworks-anthology"&gt;&lt;em&gt;The Thoughtworks Anthology&lt;/em&gt;&lt;/a&gt;, a collection of essays about software development from some of my field's luminaries. The essay argued for polyglot programming, especially since the Java Virtual Machine enjoys wide distribution; a mature, scalable implementation; and a number of languages that compile down to Java bytecode. It showed a typical "isBlank" method in Java and an implementation in Ruby (in particular, JRuby) that was one-third the length and much more readable. The end result: code that's easier to maintain overall.&lt;br /&gt;&lt;br /&gt;There were a few other reasons. If I'm laid off, Ruby knowledge would be a good resume item. I'm also drawn to the language's "Behavior Driven Development" testing tools &lt;a href="http://cukes.info/"&gt;Cucumber&lt;/a&gt; and &lt;a href="http://rspec.info/"&gt;RSpec&lt;/a&gt;, which a co-worker showed me. But the &lt;em&gt;Thoughtworks&lt;/em&gt; essay was the catalyst.&lt;br /&gt;&lt;br /&gt;I bought &lt;a href="http://pragprog.com/titles/ruby3/programming-ruby-1-9"&gt;the "pickaxe" book&lt;/a&gt; and dug in. &lt;br /&gt;&lt;br /&gt;As I've &lt;a href="http://programmingobsession.blogspot.com/2009/04/meaty-learning-projects.html"&gt;said before&lt;/a&gt;, I have a standard programming project I assign myself when I'm learning a new language: a solver for various types of word puzzles that you'd find in the monthly publication of &lt;a href="http://www.puzzlers.org"&gt;the National Puzzlers' League&lt;/a&gt;. By the end of writing it, I have a good sense of the language's syntax, I've dug into the established class library a bit, and I've even learned something about performance: The dictionary I use is 838,000 lines, which means that I often have to optimize the code that runs in the loop over those lines.&lt;br /&gt;&lt;br /&gt;I got my program up and running pretty quickly, and it's easy to see why people like Ruby. Even complicated tasks come together in some form pretty quickly in a much less verbose form than they would in, say, Java. I like that the language includes mixins, a way to fake multiple inheritance, and I like that you can add behavior to an existing class (which you can also do in Objective-C via categories) without subclassing it. I added a method to the String class with no effort.&lt;br /&gt;&lt;br /&gt;And, though I haven't tried this yet, Ruby can plug in to the Open Scripting Architecture (do they still call it that?) on Mac OS X so you can control your applications via a Ruby script. At this stage in my life, I'd prefer writing a Ruby script to writing something in AppleScript.&lt;br /&gt;&lt;br /&gt;Next, I'll try and put up a simple Ruby on Rails app. I haven't fully embraced Ruby as the one true path, but it's definitely a worthwhile addition to your programming toolkit.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-5331084285475211074?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/5331084285475211074/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/12/learning-ruby.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/5331084285475211074'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/5331084285475211074'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/12/learning-ruby.html' title='Learning Ruby'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-7951154017004015783</id><published>2009-11-29T08:46:00.000-08:00</published><updated>2009-12-04T10:59:11.217-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Philosophy'/><category scheme='http://www.blogger.com/atom/ns#' term='Spring'/><title type='text'>Knowledge Creates Possibilities</title><content type='html'>When I first left college 16 years ago &amp;mdash; a bio major with a head for computers &amp;mdash; I worked for Bank of America. I helped design software for a lucrative trading desk. One of the strongest memories I have from that time is showing a piece of software to the head of the trading desk, a tall, nervous, white-haired man who stuttered when his thoughts outpaced his ability to voice them. My boss and I had taken his requirements and created software that met them. He looked at the software and said, "That's nice. I wonder: Could it do this, too?"&lt;br /&gt;&lt;br /&gt;I don't remember what the "this" was, but it wasn't a feature he had originally mentioned. This happened again. And again. And again. Couldn't he just tell us what he wanted the first time around? It took me a year or so to realize what was happening: He couldn't define the features he wanted because he didn't know what was possible.  Once he saw one feature, it expanded his horizon of what was doable, and he could then make the mental leap to bigger, and ultimately better, feature ideas.&lt;br /&gt;&lt;br /&gt;It may be the most important thing I learned on that job.&lt;br /&gt;&lt;br /&gt;It's certainly one reason that &lt;a href="http://en.wikipedia.org/wiki/Agile_software_development"&gt;Agile programming&lt;/a&gt; made sense to me when I first learned about it: The mindset urges you to get functional software in front of customers as early as possible so that they can give feedback. The stated reason for this idea is that customers can tell you what's important and get what they want. I have always added a mental note: It also lets them know what's possible.&lt;br /&gt;&lt;br /&gt;But there's no line between users and developers here. I think everyone works this way. Well, everyone except those few people who are truly visionaries.&lt;br /&gt;&lt;br /&gt;A co-worker and I are both prototyping features that require APIs. Being modern web programmers, we're building them as RESTful APIs. REST simply means that you present data organized hierarchically by "nouns," or resources (compare &lt;a href="http://www.flickr.com/photos"&gt;flickr.com/photos&lt;/a&gt; and &lt;a href="http://www.flickr.com/photos/melissanicole"&gt;flickr.com/photos/melissanicole&lt;/a&gt;, for instance). You typically present it as XML or JSON via HTTP so that even inexperienced programmers can retrieve the data and parse it.&lt;br /&gt;&lt;br /&gt;He showed me a page he had set up at the top level of his API. It didn't reference any particular resource. Instead, it presented a list of all the calls in the API. I thought it was a good idea: Rather than have the documentation in some separate place, he had put it right in front of the user. At the time, he was updating this manually. I've maintained API documents before, and it's a pain in the ass.&lt;br /&gt;&lt;br /&gt;But seeing his page, I realized that I could generate a similar page by having the system itself analyze its own internals. Look at all the request mappings you've set up, see which ones are under the path for your API calls, and add them to the page. Voila! Documentation that keeps itself up to date. (See below if you want some of the details.)&lt;br /&gt;&lt;br /&gt;It took some effort. I dug around in Spring code, read through tips on forums, and yelled at my computer. But by the end of the day, I had a page of API documentation, in the normal format of the API, that I would never have to maintain or remember to update. New API calls would get added without me thinking about it. A simple annotation on the method that handles the request gives a human-readable definition of what the call does. (I also added an exclude capability for calls that we don't want to expose.) You could argue that the time I spent on this feature was greater than the time I would spend maintaining the documentation. That may be true.&lt;br /&gt;&lt;br /&gt;But a week later, I had an epiphany. &lt;br /&gt;&lt;br /&gt;As &lt;a href="http://programmingobsession.blogspot.com/2009/11/learning-jmeter.html"&gt;I've said&lt;/a&gt;, I'm experimenting with the idea of having load testing scripts that run early and often for new features. To that end, I had a JMeter script where I manually added the API calls as I added them to the system.&lt;br /&gt;&lt;br /&gt;See the problem?&lt;br /&gt;&lt;br /&gt;What if I could make a load testing script that would hit that top-level page and use it to figure out all the other calls that were possible? I'd have a script that would, with no maintenance on my part, load test every API call as I added it. &lt;br /&gt;&lt;br /&gt;Or when someone else added one. They wouldn't even have to know about the load testing script, and their call would get load tested.&lt;br /&gt;&lt;br /&gt;It took a bit of digging in JMeter's sparse documentation, but I got it working after a few hours. I add an API call, it shows up in the generated documentation page, and then the loadtesting script picks it up. Beyond my initial excitement of seeing the flow work, I stopped thinking about it. (Again, see below for some tips.)&lt;br /&gt;&lt;br /&gt;I felt like the head of the trading desk from all those years ago. I didn't start with a vision of a load testing script that would dynamically adjust itself. But when I implemented one feature, that script came into view. And now I will always see it as a possibility, and that knowledge will create new possibilities I can't see today.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;A Self-Documenting API&lt;/span&gt;&lt;br /&gt;I'm using Spring 3.0 as a framework, and that's what made this work. If you're in the same boat, there are a few things that will frustrate you along the way.&lt;br /&gt;&lt;br /&gt;You'll want a reference to the DefaultAnnotationHandlerMapping object that Spring maintains. However, you can't autowire a reference into your class, because Spring doesn't, by default, register the DefaultAnnotationHandlerMapping as a bean. But you can create a bean of your own for that class, and then it will work. (Note that you'll probably want to Autowire for an array of AbstractUrlHandlerMapping objects, in case you add more down the road.)&lt;br /&gt;&lt;br /&gt;DefaultAnnotationHandlerMapping will give you a map of all the request mappings in your system, but the values in that map are controllers, not methods. You'll have to use reflection to go through all the methods in the class and figure out which ones actually have the @RequestMapping annotation for the given request mapping.&lt;br /&gt;&lt;br /&gt;On that note, keep in mind that you may actually be getting a CGLIB proxy for the controller, so you'll need to get the actual class name in order to use reflection.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;A Self-Discovering Load Testing Script&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Because my API documentation page returns its results in XML, I was able to use JMeter's XPath Extractor to derive all the URLs to hit. Couple that with a ForEach controller, and you've got a loop that will iterate over each URL in the document. Put an Http Request Sampler in that loop, set the path field to the iteration variable created by the controller, and you're hitting whatever the current URL is. That's the basic format.&lt;br /&gt;&lt;br /&gt;Spring's request mapping syntax looks something like /foo/bar/{id}, where {id} gets replaced at request time by whatever's in that space. (This is, incidentally, an awesome feature.) But /foo/bar/{id} is not a useful URL. I wrote a preprocessor for the Http Request Sampler that replaces {id} with some other value. You may be clever enough to do it with a Regex preprocessor; I did it with a BeanShell script that takes the current path &amp;mdash; /foo/bar/{id} &amp;mdash; and transforms it to /foo/bar/1234. Then it puts that value into a new variable called correctedPath.&lt;br /&gt;&lt;br /&gt;JMeter allows you to use variables in the name field for a component. This is very useful. If you leave the sampler's name as Http Request Sampler, every single URL will get put in the "Http Request Sampler" test name, which will make your load testing reports useless. If you set the sampler's name to &amp;mdash; using the above example &amp;mdash; ${correctedPath}, the test name will get set to /foo/bar/1234, and each discovered URL will get its own line item in your report.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-7951154017004015783?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/7951154017004015783/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/11/knowledge-creates-possibilities.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7951154017004015783'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7951154017004015783'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/11/knowledge-creates-possibilities.html' title='Knowledge Creates Possibilities'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-8516382287728352099</id><published>2009-11-15T11:42:00.000-08:00</published><updated>2009-11-16T09:28:42.114-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><title type='text'>Learning JMeter</title><content type='html'>At work recently, I set up automated load testing for some new features I'm working on. There are teams within EA that do large-scale load testing, but I have a need they can't meet. I want to know, every single day, if our performance metrics have changed in a marked way even against a development server. If they have, the change probably came from code that was checked in that day. If performance went down, our team can find the fix quickly or acknowledge the new baseline. If performance went up, we can figure out why and see if the fix can be applied in other places.&lt;br /&gt;&lt;br /&gt;I downloaded &lt;a href="http://jakarta.apache.org/jmeter/"&gt;JMeter&lt;/a&gt; and started giving myself a crash course on the software. I've used it in the past, but it was much simpler in those days. So were my requirements.&lt;br /&gt;&lt;br /&gt;A simple case was pretty easy: hit a couple URLs that don't require parameters but do return dynamic data. I set up a thread group, the "how many threads should I use" required container, then I added HTTP requests that hit the URLs to that thread group, and then I added a "listener" that parses the responses. That gave me basic familiarity with the tool and allowed me to set up an automatic, nightly run of any checked-in JMeter tests. We use &lt;a href="https://hudson.dev.java.net/"&gt;Hudson&lt;/a&gt; as a build system, and it has a nice little JMeter plugin that will graph the response times over time and also give you a simple per-run breakdown of performance.&lt;br /&gt;&lt;br /&gt;But then I wanted a test suite that would hit a URL, parse the response, and run subsequent tests based on the data (an ID) that came back in the first response. There are plenty of hints that JMeter can do this kind of thing, but it took a lot of muddling to get it right.&lt;br /&gt;&lt;br /&gt;I often say that I should contribute to open-source projects not as a professional programmer but as a professional writer. Documentation is often sparse and unclear, even on an established project like JMeter. Where is the "cookbook" section with "you want to do this common thing; here's how" entries? Where is the list of best practices? &lt;br /&gt;&lt;br /&gt;You read their manual, you read the FAQ and the wiki, and you still spend a lot of time bumbling about, poking this and prodding that to see if the errors clear up. And I've barely touched the more powerful features.&lt;br /&gt;&lt;br /&gt;Here are a few of the things I learned along the way, which I'm posting mostly for my own benefit.&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;JMeter can't do anything with a response &amp;mdash; even show it to you &amp;mdash; without knowing the MIME type. On the one hand, that forced me to return a clean response. On the other hand, I feel like JMeter should fall back to plain text if it has no other information.&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;Extract data from a response with a post-processor. If you want to use the response data from one http request in subsequent calls, you make the post-processor a child of that http request. (You can make a post-processor a sibling, in which case it runs after every request in the thread group.) I used a regular expression post-processor that matched the entire response. Even though there was no other data, I still needed to enclose the regex in &lt;code&gt;()&lt;/code&gt;. I also needed the &lt;code&gt;$1$&lt;/code&gt; default template, even though I wasn't doing anything with multiple values. &lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;It seems like every JMeter tutorial suggests that you add a Graph Results listener to your test plan. It shows you a graph of your response time. Ooh. Aah. Pretty. Also? Useless for debugging. I moved through errors at a rapid pace after I added a View Results Tree listener to the thread group. With that listener, you can drill down on the request/response for each and every server call. I also found the Summary Report listener to be more useful than Graph Results. Keep the pretty picture in to show your boss &amp;mdash; it doesn't add time &amp;mdash; but add the others to make your life easier.&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;Learn to love the JMeter functions. They're buried in &lt;a href="http://jakarta.apache.org/jmeter/usermanual/functions.html"&gt;Chapter 19 of the JMeter manual&lt;/a&gt;, but they're essential for making scripts that can be re-used in multiple places. I sprinkled the ${__P(name)} function throughout the text fields of my scripts so that I could fetch command-line properties such as TARGET_SERVER and TARGET_PORT. That means that running my scripts against a different environment will require nothing more than a different set of command-line arguments (prefaced with -J).&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-8516382287728352099?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/8516382287728352099/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/11/learning-jmeter.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/8516382287728352099'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/8516382287728352099'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/11/learning-jmeter.html' title='Learning JMeter'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-3461658278845565012</id><published>2009-11-13T09:20:00.000-08:00</published><updated>2009-11-13T09:59:50.139-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Spring'/><title type='text'>Profiling With Spring's AOP</title><content type='html'>Recently, at work, I wanted to set up an easy way to get lightweight profiling information about method calls in our system. There are lots of heavyweight profilers out there that give you great features at the cost of bogging down the machine they're profiling. Fine if it's a development box; less so if it's a production machine your users are using.&lt;br /&gt;&lt;br /&gt;Because we're using Spring, we have access to a pretty decent aspect-oriented-programming system. (&lt;a href="http://en.wikipedia.org/wiki/Aspect-oriented_programming"&gt;AOP&lt;/a&gt; is basically a way to add system-wide concerns to objects without those objects having to be aware of the systems in any way: no subclassing or encapsulation.) Since profiling is a classic cross-cutting concern &amp;mdash; something you need throughout the system but don't want any individual object to know anything about &amp;mdash; Spring's mechanism works pretty well.&lt;br /&gt;&lt;br /&gt;In my final system, a programmer only needs to add a &lt;code&gt;@Profile&lt;/code&gt; annotation on a method, and it will generate profiling information in the logs. I didn't want to profile every method all the time because of the noise it would generate and the performance hit it would cause. The programmer can refine the output of the profiling system a bit, but the default is to show method name, args, return value, and timing information.(Spring's AOP framework is limited to non-private methods that are proxied by the Spring system, so private methods and intraobject calls don't get tracked. If that becomes a problem, I'll add in true &lt;a href="http://en.wikipedia.org/wiki/AspectJ"&gt;AspectJ&lt;/a&gt; support, but that shouldn't require any real changes in my code, since Spring's support uses AspectJ syntax.)&lt;br /&gt;&lt;br /&gt;Here are the key parts.&lt;br /&gt;&lt;br /&gt;First, the profiling method, with a pointcut/advice combination that says "run this code &lt;em&gt;around&lt;/em&gt; any method annotated with a &lt;code&gt;@Profile&lt;/code&gt; annotation.&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    @Around("@annotation(profileAnnotation)") &lt;br /&gt;    public Object profileMethod(ProceedingJoinPoint pjp, &lt;br /&gt;                Profile profileAnnotation) throws Throwable {&lt;br /&gt;        &lt;br /&gt;        StringBuilder buf = new StringBuilder("method: " + &lt;br /&gt;                               pjp.getSignature().getName());&lt;br /&gt;        if (profileArgs(profileAnnotation)) {&lt;br /&gt;            buf.append("; args: ");&lt;br /&gt;            if (pjp.getArgs() != null &amp;&amp; &lt;br /&gt;                pjp.getArgs().length &gt; 0) {&lt;br /&gt;                buf.append("[");&lt;br /&gt;                for (Object o : pjp.getArgs()) {&lt;br /&gt;                    buf.append(o.toString() + ",");&lt;br /&gt;                }&lt;br /&gt;                buf.append("]");&lt;br /&gt;            } else {&lt;br /&gt;                buf.append(" (no args)");&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        long currentTime = System.currentTimeMillis();&lt;br /&gt;        Object retVal = pjp.proceed();&lt;br /&gt;        if (profileReturnValue(profileAnnotation)) {&lt;br /&gt;            buf.append("; returnValue: " + &lt;br /&gt;                        String.valueOf(retVal)); // valueof prevents NPEs&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        if(profileTiming(profileAnnotation)) {&lt;br /&gt;            buf.append("; timing: " + (System.currentTimeMillis() - currentTime));&lt;br /&gt;        }&lt;br /&gt;        log.info(buf.toString());&lt;br /&gt;        return retVal;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The Profile annotation looks like this:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;/** Annotation to demark methods that should be profiled. */&lt;br /&gt;@Retention( RetentionPolicy.RUNTIME )&lt;br /&gt;@Target( ElementType.METHOD )&lt;br /&gt;&lt;br /&gt;public @interface Profile {&lt;br /&gt;    ProfilingType[] value() default {ProfilingType.ALL};&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And the ProfilingType enum looks like this:&lt;br /&gt;&lt;code&gt;public enum ProfilingType {ARGS,RETURN,TIMING,ALL}&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The default output (&lt;code&gt;ProfilingType.ALL&lt;/code&gt;) looks something like this:&lt;br /&gt;&lt;code&gt;method: [methodName]; args: [each arg as String]; returnValue: [returnValue]; timing: [some number of milliseconds]&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-3461658278845565012?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/3461658278845565012/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/11/profiling-with-springs-aop.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/3461658278845565012'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/3461658278845565012'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/11/profiling-with-springs-aop.html' title='Profiling With Spring&apos;s AOP'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-665198923088273925</id><published>2009-11-05T17:14:00.000-08:00</published><updated>2009-11-05T17:18:52.530-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Curmudgeon'/><title type='text'>Sad, But Probably True</title><content type='html'>Reading the website for &lt;a href="http://www.packtpub.com/beginning-apache-jmeter/book"&gt;&lt;em&gt;Apache Jmeter&lt;/em&gt;&lt;/a&gt;, I noticed this line: "JMeter's target audience is the testing community, which is generally not the hangout of developers or technical people."&lt;br /&gt;&lt;br /&gt;Except for those developers who understand that the sooner you catch an issue, the cheaper it is to fix. Which, granted, is not as many as there should be.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-665198923088273925?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/665198923088273925/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/11/sad-but-probably-true.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/665198923088273925'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/665198923088273925'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/11/sad-but-probably-true.html' title='Sad, But Probably True'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-7797417322549507588</id><published>2009-10-07T19:56:00.001-07:00</published><updated>2009-10-07T20:36:33.186-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><title type='text'>Rounded Corners on UITableView</title><content type='html'>The iPhone UI is a soft, graceful collection of rounded corners. Your application icon is cropped and smoothed out with the ubiquitous curves. The highlight on the selected tab of a tab bar has tiny ones. Grouped table views, the corners of navigation items, the little buttons in toolbars. You can't escape the smooth arcs.&lt;br /&gt;&lt;br /&gt;Which would be fine if Apple didn't require you to hand craft them yourself on your UI components. You might expect a "draw this with rounded corners" method. Actually, given their prevalence, you'd expect it to be a setting on UIView: &lt;code&gt;cornerRadius&lt;/code&gt; or something like that.&lt;br /&gt;&lt;br /&gt;But since that doesn't exist, every developer has to write his or her own take on it. So far, mine has become fairly standard. My UIView classes implement &lt;code&gt;drawRect&lt;/code&gt; to set a curvy clip region. I have a utility method that takes any rect, with a desired radius, and returns a path for a rounded rectangle. Add that to the graphics context, call &lt;code&gt;CGContextClip&lt;/code&gt;, and voila, I have voluptuousness. This has worked well.&lt;br /&gt;&lt;br /&gt;Then I tried to do it on a UITableView embedded in a larger view. It's a view, right? So this should work, right? Except that UITableView's &lt;code&gt;drawRect&lt;/code&gt; method doesn't really do anything. The corners don't get clipped if you subclass it and clip within &lt;code&gt;drawRect&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Searching on this topic suggests that this is a common problem. I read various ideas, didn't really like them for one reason or another, and then came up with my own strategy. Why not overlay my table with a view that would act as a window? It would draw the part that I normally clip off and then make the interior transparent. And with a bit of work, I could make it a reusable view I could put atop other views that didn't behave the way I wanted. No more mucking around with clipping in random subviews!&lt;br /&gt;&lt;br /&gt;&lt;a href="http://programmingobsession.blogspot.com/2009/05/i-am-not-graphics-programmer.html"&gt;I am not a graphics programmer.&lt;/a&gt; Not only did this take me a while, but there are probably better ways to do it. However, you may find yourself wondering how to do the same thing, and so I'm sharing my results.&lt;br /&gt;&lt;br /&gt;Here's my init method. I used &lt;code&gt;bgColor&lt;/code&gt; to represent the color on the outside of the corners, and I set the actual &lt;code&gt;backgroundColor&lt;/code&gt; to &lt;code&gt;clearColor&lt;/code&gt; I tried using &lt;code&gt;clearColor&lt;/code&gt; as the fill color for my rounded path, but that didn't work. It was transparent over the view's background color, which meant the whole rectangle was filled in with the view's background, not the view underneath.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;- (id)initWithFrame:(CGRect)frame {&lt;br /&gt;    if (self = [super initWithFrame:frame]) {&lt;br /&gt;        bgColor = [UIColor blackColor].CGColor;&lt;br /&gt;        self.backgroundColor = [UIColor clearColor];&lt;br /&gt;        self.userInteractionEnabled = YES;&lt;br /&gt;    }&lt;br /&gt;    return self;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And here's the drawing code. I used EOFillPath to ensure that my interior was empty. Yes, I hardcoded the radius. Yes, that's lame. I'll fix it in a refactor pass.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;- (void)drawRect:(CGRect)rect {&lt;br /&gt;    // Drawing code&lt;br /&gt;    CGContextRef ctxt = UIGraphicsGetCurrentContext();&lt;br /&gt;    &lt;br /&gt;    CGContextSetFillColorWithColor(ctxt,bgColor);&lt;br /&gt;    CGContextAddRect(ctxt,rect);&lt;br /&gt;    CGPathRef curvePath = [Utils roundedCornerPathFromRect:rect withRadius:10.0];&lt;br /&gt;    CGContextAddPath(ctxt, curvePath);&lt;br /&gt;    CGPathRelease(curvePath);&lt;br /&gt;    &lt;br /&gt;    CGContextEOFillPath(ctxt);&lt;br /&gt;    [super drawRect: rect];&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;That's the result of a fair amount of fiddling, and it now works. Except for one problem: You've covered the view you want the user to see with another view, effectively removing the user's ability to interact with it. To get around this, I created a "coveredView" reference within RoundedCornerOverlay and set it in the view controller managing the whole view. I overrode &lt;code&gt;nextResponder&lt;/code&gt; in my view to return this reference. Then I overrode the various event methods you inherit from &lt;code&gt;UIView&lt;/code&gt;. The final step was to realize I needed to convert incoming points to be relevant within the covered view.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {&lt;br /&gt;    [[self nextResponder] touchesBegan:touches withEvent:event];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {&lt;br /&gt;    [[self nextResponder] touchesMoved:touches withEvent:event];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {&lt;br /&gt;    [[self nextResponder] touchesEnded:touches withEvent:event];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {&lt;br /&gt;    [[self nextResponder] touchesCancelled:touches withEvent:event];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {&lt;br /&gt;    return [[self coveredView] &lt;br /&gt;             hitTest:[self convertPoint:point toView:[self coveredView]] withEvent:event];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {&lt;br /&gt;    return [[self coveredView] &lt;br /&gt;            pointInside:[self convertPoint:point toView:[self coveredView]] withEvent:event];&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-7797417322549507588?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/7797417322549507588/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/10/rounded-corners-on-uitableview.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7797417322549507588'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7797417322549507588'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/10/rounded-corners-on-uitableview.html' title='Rounded Corners on UITableView'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-1972943638581831848</id><published>2009-10-03T17:31:00.000-07:00</published><updated>2009-10-03T17:38:45.897-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Announcement'/><title type='text'>Independent Developer</title><content type='html'>I assume that if anyone reads this blog &amp;mdash; anyone? hello? &amp;mdash; they also read my main blog, &lt;a href="http://www.obsessionwithfood.com"&gt;An Obsession With Food&lt;/a&gt;. So you've probably already seen the announcement: &lt;a href="http://www.obsessionwithfood.com/2009_09_01_blog-archive.html#6540560663435895910"&gt;I launched 1.0 of my iPhone app, which helps home cooks organize the prep tasks for a dish&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;What that really means is that for the first time in my professional career, I'm an independent developer. Of course, I also work for a company that can pay me. I am not planning my retirement based around sales of my application in the App Store. But I'll be the one doing the marketing &amp;mdash; ugh &amp;mdash; tech support, and project management. Now I'll have users. At least I hope I will.&lt;br /&gt;&lt;br /&gt;So far, I've been the primary user of this app. In a way, this is good, because it's forced me to use the app day in and day out, finding the things that annoy me and then fixing them. But it's also bad. The app is tailored to the way I do things, even though most people probably have different menu planning strategies. I've had testers, of course, who have offered feedback. But, as of today, I have four times as many users as I did before I launched. I'm hoping they'll take the time to write me and offer suggestions, but who knows what will happen?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-1972943638581831848?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/1972943638581831848/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/10/independent-developer.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/1972943638581831848'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/1972943638581831848'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/10/independent-developer.html' title='Independent Developer'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-8054176356964231564</id><published>2009-09-14T08:42:00.000-07:00</published><updated>2009-09-14T10:12:05.788-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Philosophy'/><title type='text'>The Simplest Thing That Could Possibly Work</title><content type='html'>One of the maxims of Agile development &amp;mdash; and especially Extreme Programming &amp;mdash; is to write the simplest thing that can possibly work.&lt;br /&gt;&lt;br /&gt;There are a few driving forces behind this motto. First, a core tenet of Agile development is to get testable, functional code up and running as soon as possible. If you don't spend a lot of time writing complex code, you can get it running sooner. And complex code is harder to maintain and thus more likely to have subtle bugs. Second, Agile assumes that requirements will change. This in part because requirements always change, but also because the mere act of showing a user a current piece of software will give them new ideas for the features that will really solve their problems. Users aren't good at articulating what they need until they see something "tangible" that &lt;em&gt;isn't&lt;/em&gt; what they need. If you get a piece of code up sooner, you give your users the chance to steer the application in the direction they really want, not the direction they thought they wanted. And when they present this abrupt change in direction &amp;mdash; as they inevitably will &amp;mdash; you don't have to dismantle a complex system.&lt;br /&gt;&lt;br /&gt;A close cousin of the "simplest thing" motto is "You're Not Gonna Need It." Experienced programmers tend to think in terms of frameworks they can build, good object-oriented principles, and so forth. But if you spend a lot of time building a nice, modular system with layers and abstraction, you may have wasted your time and effort if there will only ever be one module, one implementation of a given interface, or one class in a layer.&lt;br /&gt;&lt;br /&gt;This is not to say that you should be sloppy, or churn out quick and dirty, copy-paste code. You are a professional and should act like one. Your "simplest thing" should be tested and it should be clean code. As the requirements change beyond what your "simplest thing" can do, you can then start to build layers on top of it, refactor to make it more modular, and so on.&lt;br /&gt;&lt;br /&gt;All these arguments came back to me when I had to implement templatable text at work. I wanted text that a non-programmer could modify, but it needed to allow variable substitution.&lt;br /&gt;&lt;br /&gt;Java's localization system allows this to some degree, except that its variables are numeric, not symbolic. So item zero is always the same, item one is always the same, etc. regardless of where they show up in the string. This makes sense for localization, but is fragile for more general templates. What if we wanted the text to use a different set of variables from one week to the next?&lt;br /&gt;&lt;br /&gt;There are a number of general-purpose templating systems for Java: Probably the best-known is &lt;a href="http://velocity.apache.org/"&gt;Velocity&lt;/a&gt;. As I started researching what it would take to implement it, however, the "simplest thing" motto came back to mind. Not that Velocity is difficult to implement, but we only needed to support five variables, and we didn't need any of the flow-of-control logic that Velocity offers.&lt;br /&gt;&lt;br /&gt;So I wrote a very simple set of methods that allows me to do string replaces on the five symbolic variables I allowed. The (internal) user can write any text, using some or all of those variables once, twice, or whatever they want. And just in case we ever need to move to Velocity, I used the same variable naming syntax to define our variables.&lt;br /&gt;&lt;br /&gt;The code is simple. It has noticeable restrictions (can't use undefined variables, can't define new ones), but in this case, it works exactly the way I need it to. It satisfied the business need, and truthfully, will probably never need to support more. When I needed to add an additional piece of templated text with the same variables, it took about 15 minutes including 10 minutes of testing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-8054176356964231564?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/8054176356964231564/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/09/simplest-thing-that-could-possibly-work.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/8054176356964231564'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/8054176356964231564'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/09/simplest-thing-that-could-possibly-work.html' title='The Simplest Thing That Could Possibly Work'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-4505569758300451982</id><published>2009-09-04T00:07:00.000-07:00</published><updated>2009-09-04T08:11:43.322-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><title type='text'>Tutorial System, Part 2: Variadic Methods</title><content type='html'>In &lt;a href="http://programmingobsession.blogspot.com/2009/05/tutorial-mode-for-iphone-app.html"&gt;my last post&lt;/a&gt;, I mentioned a simple tutorial system that I wrote for my iPhone app: When I pass a key to the system, new users get a text box explaining the purpose of each screen, tap to close it, and then never see it again.&lt;br /&gt;&lt;br /&gt;It worked well enough, though my testers have some usability comments that I need to address. But as I started working on version 1.1 of my app*, I realized that I had new needs. I had written, as I try to remember to do despite the temptation to overdesign, the simplest thing that could possibly work. That no longer fit the bill. (&lt;a href="http://c2.com/xp/DoTheSimplestThingThatCouldPossiblyWork.html"&gt;Writing the simplest thing that can possibly work&lt;/a&gt; assumes that design requirements will change. But since you can't predict those changes, even when you're the one creating them, you shouldn't waste time designing for requirements that don't yet exist. Address the problem in front of you, get functional code up and testable, and then refactor to simplify.)&lt;br /&gt;&lt;br /&gt;I wanted the system to handle upgraders, so that upgraders would see text just about the new features, while new users would see a screen giving an overview of the page, including the new features. The key was that when a new user dismissed the tutorial box, s/he would never see the one for upgraders (because there would be no need).&lt;br /&gt;&lt;br /&gt;I toyed with a few ideas, but I finally settled on one: Rather than passing a single key to the system, I'd pass a list of keys. The system would iterate through the keys until it found one the user hadn't seen, show the tutorial text for that key, register that the user had seen the key, and then register every subsequent key in the list.&lt;br /&gt;&lt;br /&gt;In order to make this work, I used a &lt;a href="http://en.wikipedia.org/wiki/Variadic_function"&gt;variadic method&lt;/a&gt;, one with an indeterminate number arguments. It's been a while since I've been deep in a C-based language, so I had to refresh my memory about implementing them. (Curiously, the Objective-C bible, &lt;em&gt;Programming in Objective-C&lt;/em&gt;, doesn't tell you how to implement them. Perhaps the new edition does.) They rely on the stdargs.h macros, &lt;code&gt;va_start&lt;/code&gt;, &lt;code&gt;va_arg&lt;/code&gt;, and &lt;code&gt;va_end&lt;/code&gt;. In Objective-C, the convention is that the last item in the list be nil. For example, see NSArray's &lt;a href="http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html#//apple_ref/occ/instm/NSArray/initWithObjects"&gt;&lt;code&gt;initWithObjects:&lt;/code&gt;&lt;/a&gt; method.&lt;br /&gt;&lt;br /&gt;If you want to implement your own variadic method in Objective-C, here's the basic pattern (you don't need to import stdarg.h if you're importing the Foundation classes):&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; - (void) method: (id) arg, ... {&lt;br /&gt;        va_list argList; // special datatype for the argument list&lt;br /&gt;        va_start(argList,arg); // sets up the iteration&lt;br /&gt;       &lt;br /&gt;       id curArg = arg;&lt;br /&gt;       while (curArg != nil) {&lt;br /&gt;             // do something&lt;br /&gt;             curArg = va_arg(argList,id); // pass the datatype you expect to find&lt;br /&gt;       }&lt;br /&gt;       va_end(argList);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Here's the code in the context of my Tutorial system:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;+ (void) showTutorialForKey: (NSString *)key, ... {&lt;br /&gt;    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];&lt;br /&gt;    va_list argPtr;&lt;br /&gt;    va_start(argPtr,key);&lt;br /&gt;    &lt;br /&gt;    NSString *curArg = key;&lt;br /&gt;    &lt;br /&gt;    while (curArg != nil) {&lt;br /&gt;        if ([TutorialSystem userHasSeenKey:curArg]) {&lt;br /&gt;            curArg = va_arg(argPtr,id);&lt;br /&gt;            continue;&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        NSString *titleKey = [NSString stringWithFormat:@"%@ Title",curArg];&lt;br /&gt;        &lt;br /&gt;        NSString* title = &lt;br /&gt;          [[NSBundle mainBundle] localizedStringForKey:titleKey value:@"" table:@"Tutorial"];&lt;br /&gt;        NSString* text = &lt;br /&gt;          [[NSBundle mainBundle] localizedStringForKey:curArg value:@"" table:@"Tutorial"];&lt;br /&gt;        &lt;br /&gt;        TutorialController *controller = &lt;br /&gt;          [[TutorialController alloc] initWithTitle:title tutorialText:text];&lt;br /&gt;        [[[self appDelegate] window] addSubview:[controller view]];&lt;br /&gt;        &lt;br /&gt;        // clear the user for all the rest of the args&lt;br /&gt;        while (curArg != nil) {&lt;br /&gt;            [TutorialSystem registerUserSeesKey:curArg];&lt;br /&gt;            curArg = va_arg(argPtr,id);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    va_end(argPtr);&lt;br /&gt;    [pool release];&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;* I should note that 1.0 of my app is not yet out, despite being published on the App Store, because of some bureaucracy with my employer. Assuming it's going to get resolved. I've started on 1.1.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-4505569758300451982?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/4505569758300451982/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/09/tutorial-system-part-2-variadic-methods.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4505569758300451982'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4505569758300451982'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/09/tutorial-system-part-2-variadic-methods.html' title='Tutorial System, Part 2: Variadic Methods'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-1136079986715747727</id><published>2009-05-31T09:33:00.000-07:00</published><updated>2009-05-31T10:16:42.877-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><title type='text'>Tutorial Mode For An iPhone App</title><content type='html'>One of my testers suggested that I have a "tutorial" for new users of my app: Help screens that pop up the first time a user visits a feature. There are many ways to do this, of course, but here's the implementation I designed. A client invokes the tutorial system in a simple way:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;      [TutorialSystem showTutorialForKey:@"Welcome"];&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;TutorialSystem&lt;/code&gt; class takes over the rest through a series of class methods. (There's never an instance of a &lt;code&gt;TutorialSystem&lt;/code&gt; object. Not a very object-oriented approach, but the tutorial system doesn't need to maintain state between invocations.)&lt;br /&gt;&lt;br /&gt;Obviously I only want to show a tutorial screen once, so &lt;code&gt;showTutorialForKey:&lt;/code&gt; first checks to see if the user has already seen the screen. I have an "application state" dictionary file that gets used for miscellaneous persistence: the last tab the user looked at before shutting down, the date they were looking at in the "by day" view, and so forth. The tutorial system uses the same dictionary to keep track of which tutorial screens the user has seen. In particular, it looks for a key named "hasSeen" followed by the name of the key passed in to the &lt;code&gt;showTutorialForKey&lt;/code&gt; method. For the example above, the dictionary would have a key named "hasSeenWelcome."&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;+ (BOOL) userHasSeenKey: (NSString *)key {&lt;br /&gt;    if (ALWAYS_SHOW_HELP) {&lt;br /&gt;        return NO;&lt;br /&gt;    }&lt;br /&gt;    NSMutableDictionary *prefs = [[TutorialSystem appDelegate] appState];&lt;br /&gt;    NSNumber *didSee = [prefs objectForKey:[TutorialSystem prefsSeenKeyForKey:key]];&lt;br /&gt;    &lt;br /&gt;    return didSee != nil &amp;&amp; [didSee intValue] != 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;(I have a #define that lets me always have tutorial screens show up. This is helpful for testing.)&lt;br /&gt;&lt;br /&gt;If the system decides that it needs to show the tutorial screen, it uses the passed-in key as the key for a line in a strings file, the resource files Cocoa uses for localization of text. It also uses the key plus the text " Title" as the key in the strings file for the title of the tutorial screen. That means there's a line in my strings file that maps the tutorial body text to the key "Welcome" and another line that maps the tutorial screen title to "Welcome Title."&lt;br /&gt;&lt;br /&gt;From there, it loads an xib file that I built in Interface Builder and lays it over the existing view. That file contains a screen-sized view that doesn't allow user interaction, but has an opaqueness level of .4. That way the underlying screen will still be visible. The xib file also contains a "tutorial screen" view that contains three subviews: a UILabel for the title, a UITextView for the tutorial body, and a UILabel that says "Tap to continue." The tutorial screen view handles all the user interactions, and interprets any touch to mean "go away." Here's what it looks like.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_2GWIyzkZYjQ/SiK5sVAoojI/AAAAAAAAACM/ig4M7mE-9rA/s1600-h/Picture+8.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 315px; height: 351px;" src="http://2.bp.blogspot.com/_2GWIyzkZYjQ/SiK5sVAoojI/AAAAAAAAACM/ig4M7mE-9rA/s400/Picture+8.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5342036279220019762" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Finally, of course, I register that the user has seen the tutorial screen by placing the appropriate entry into the application state dictionary.&lt;br /&gt;&lt;br /&gt;Here's the full &lt;code&gt;showTutorialForKey:&lt;/code&gt; method:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;+ (void) showTutorialForKey: (NSString *)key {&lt;br /&gt;    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];&lt;br /&gt;    if ([TutorialSystem userHasSeenKey:key]) {&lt;br /&gt;        [pool release];&lt;br /&gt;        return;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    NSString *titleKey = [NSString stringWithFormat:@"%@ Title",key];&lt;br /&gt;    &lt;br /&gt;    NSString* title = [[NSBundle mainBundle]&lt;br /&gt;                                   localizedStringForKey:titleKey value:@"" table:@"Tutorial"];&lt;br /&gt;    NSString* text = [[NSBundle mainBundle] &lt;br /&gt;                                           localizedStringForKey:key value:@"" table:@"Tutorial"];&lt;br /&gt;    &lt;br /&gt;    TutorialController *controller = &lt;br /&gt;                               [[TutorialController alloc] initWithTitle:title tutorialText:text];&lt;br /&gt;    [[[self appDelegate] window] addSubview:[controller view]];&lt;br /&gt;    &lt;br /&gt;    [TutorialSystem registerUserSeesKey:key];&lt;br /&gt;    &lt;br /&gt;    [pool release];&lt;br /&gt;    &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-1136079986715747727?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/1136079986715747727/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/tutorial-mode-for-iphone-app.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/1136079986715747727'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/1136079986715747727'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/tutorial-mode-for-iphone-app.html' title='Tutorial Mode For An iPhone App'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_2GWIyzkZYjQ/SiK5sVAoojI/AAAAAAAAACM/ig4M7mE-9rA/s72-c/Picture+8.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-3100012010072877177</id><published>2009-05-28T07:43:00.000-07:00</published><updated>2009-05-28T07:50:41.009-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><title type='text'>Simulator Versus Device</title><content type='html'>I had an amusing realization this morning as I finished up a feature for my iPhone app. I implemented a "touch and hold" mechanism (see &lt;a href="http://www.iphonedevsdk.com/forum/iphone-sdk-development/1919-solution-how-recognize-touch-hold.html"&gt;this excellent description of how to do it&lt;/a&gt;) and tested it last night on the simulator, Apple's "virtual iPhone" that makes it faster to turn around and test new code. It worked perfectly.&lt;br /&gt;&lt;br /&gt;This morning, I put it on my phone, and it barely worked at all. It took me a few minutes, but I finally figured out the issue.&lt;br /&gt;&lt;br /&gt;In my original code, &lt;code&gt;touchesMoved&lt;/code&gt; dismissed the timer that eventually puts an indicator in place that you're in editing mode. On the simulator, that's not a problem: You take your finger off the mouse. But in the real world, your finger does not sit perfectly still. Mine doesn't, at any rate. So I had to add literal wiggle room. If you stay within a small radius of touches, the code won't dismiss the timer. Now it works just the way I want.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-3100012010072877177?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/3100012010072877177/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/simulator-versus-device.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/3100012010072877177'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/3100012010072877177'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/simulator-versus-device.html' title='Simulator Versus Device'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-2117162100159610375</id><published>2009-05-24T07:32:00.000-07:00</published><updated>2009-05-24T09:36:08.363-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='Philosophy'/><title type='text'>I Am Not a Graphics Programmer</title><content type='html'>I consider myself a good programmer. I can comfortably chat about anything from high-level topics such as methodologies and architecture to the minutiae of multithreaded code and Java virtual machine idiosyncracies. I understand how to solve a wide range of scalability issues. I have a good eye for the subtle details that cause bugs. I know at least the basics of when one algorithm is better suited for a task than another.&lt;br /&gt;&lt;br /&gt;Put me in front of a graphics library, however, and I freeze up. I've been doing some simple graphics work in my iPhone app &amp;mdash; filling an area with a gradient, for instance &amp;mdash; and it's been slow going. I don't have the terminology in my head for this work to come naturally. Transforms, alpha channels, drawing paths, blending modes. I might as well be reading Greek. And it doesn't help that Quartz, the graphics drawing system on the iPhone, is all based on C, a procedural language, instead of Objective C, an object-oriented language. It's been a very long time since I've worked in a low-level procedural language on a regular basis. (I will say, though, that Quartz tries its best to encapsulate the C code in macros that provide a straightforward coding experience. The graphics folks around me at Maxis think that it's pretty sweet, which makes me wonder what sort of libraries Microsoft gives to Windows programmers.)&lt;br /&gt;&lt;br /&gt;Some of my frustration comes from simple lack of experience. I didn't used to know a lot about server-side programming, after all. As I do more graphics programming, I suppose more of it will come naturally, and I will learn more of the subtleties. But I've also never been drawn to that side of the coding fence. I like to say that I have no design sense. This isn't strictly true: I did the design work for &lt;a href="http://www.obsessionwithfood.com"&gt;Obsession With Food&lt;/a&gt;, and I've got some stunning quilt tops that I designed. But I'm always surprised when something I design comes out well.&lt;br /&gt;&lt;br /&gt;Yet here I am, literally a one-man development team for this application. I can manage the architecture just fine &amp;mdash; and it's pretty good &amp;mdash; but there's no one I can hand off the graphics work to. It's just me. I speed through the object-oriented development side and slow to a crawl when I'm dealing with Quartz. In the long run, it's good for me to expand my horizons. But in the short term, the part of me that's used to quickly getting code up and running is growling at the long periods of time it takes me to implement the simplest little things in the graphics world.&lt;br /&gt;&lt;br /&gt;Back to figuring out why my transparent blue is sometimes the only thing in the rectangle and is sometimes on top of the gradient that's supposed to be gone at that point. And why doesn't my grey rectangle show up at all? All this for a tiny-but-necessary bit of visual feedback on an already functional feature.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-2117162100159610375?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/2117162100159610375/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/i-am-not-graphics-programmer.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/2117162100159610375'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/2117162100159610375'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/i-am-not-graphics-programmer.html' title='I Am Not a Graphics Programmer'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-247781578885937153</id><published>2009-05-22T16:32:00.001-07:00</published><updated>2009-05-22T16:33:26.525-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='When I Have Time'/><title type='text'>Federal Data Coming to a Web App Near You</title><content type='html'>The Federal government is releasing tons of data in web-friendly formats. See the perfectly named &lt;a href="http://www.data.gov/"&gt;data.gov&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Must. Finish. One. App. Before. Starting. Another.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-247781578885937153?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/247781578885937153/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/federal-data-coming-to-web-app-near-you.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/247781578885937153'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/247781578885937153'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/federal-data-coming-to-web-app-near-you.html' title='Federal Data Coming to a Web App Near You'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-2870020752776574121</id><published>2009-05-15T13:53:00.000-07:00</published><updated>2009-05-15T19:51:34.415-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><title type='text'>Real-World iPhone Rotation, Part 1</title><content type='html'>When I researched the techniques for supporting rotation (portrait vs. landscape) in my iPhone app, I found a few tutorials that all did the same thing: Stick a label in the middle of an otherwise unadorned view and override one method. &lt;br /&gt;&lt;br /&gt;Simple, yes? Yes. If you're writing a boring application that just has one line of text in the middle of the screen.&lt;br /&gt;&lt;br /&gt;My application has a tab bar. It has multiple screens. It has custom views that pop up from the bottom of the screen. It is not well served by the tutorials I found. Here, then, is one part of &lt;em&gt;my&lt;/em&gt; tutorial, based on my own experience.&lt;br /&gt;&lt;br /&gt;First, the default mode for any UIViewController is to &lt;em&gt;not&lt;/em&gt; support rotation. It's easy to change that if you have a custom class that inherits from UIViewController: Override &lt;code&gt;shouldAutorotateToInterfaceOrientation:&lt;/code&gt; to return &lt;code&gt;YES&lt;/code&gt;. (You can get fancy with the argument passed in and only support certain orientations, but I didn't.)&lt;br /&gt;&lt;br /&gt;But what if you're using a UITabBarController? The same idea applies, unfortunately. Make a subclass of UITabBarController and override that method. In Interface Builder, then, set your custom class as the handler. My class, &lt;code&gt;RotatingTabController&lt;/code&gt;, does nothing except override that method and return &lt;code&gt;YES&lt;/code&gt;. Yay for class bloat! There may be some way to just set a property in a normal tab bar controller and have it magically start handling rotation, but I haven't found it. So a brand-new class that exists solely to override a boolean return value it is.&lt;br /&gt;&lt;br /&gt;However, once you've overridden that method for UITabBarController, you don't need to do it for all the views that hang off of it. But you do need to make sure your views behave when rotated.&lt;br /&gt;&lt;br /&gt;The rotation animation on the iPhone is snazzy: Views arc around the screen and rearrange themselves. Get too wrapped up in the idea that you're rotating the view, however, and you'll hurt your head. Instead, think of it as resizing the view, since that's actually what it's doing. It's much easier to grok if you imagine going from this:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_2GWIyzkZYjQ/Sg4kVkflShI/AAAAAAAAAB0/qPJGDiSP2Bg/s1600-h/Picture+3.png"&gt;&lt;img style="margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 277px; height: 400px;" src="http://4.bp.blogspot.com/_2GWIyzkZYjQ/Sg4kVkflShI/AAAAAAAAAB0/qPJGDiSP2Bg/s400/Picture+3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5336242561472612882" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;to this:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_2GWIyzkZYjQ/Sg4ktVEVBXI/AAAAAAAAAB8/BFk-ARpxd2Y/s1600-h/Picture+4.png"&gt;&lt;img style="; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 400px; height: 246px;" src="http://3.bp.blogspot.com/_2GWIyzkZYjQ/Sg4ktVEVBXI/AAAAAAAAAB8/BFk-ARpxd2Y/s400/Picture+4.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5336242969648629106" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_2GWIyzkZYjQ/Sg4nVK76GHI/AAAAAAAAACE/aK5ueitr8Tk/s1600-h/Picture+5.png"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 237px; height: 117px;" src="http://3.bp.blogspot.com/_2GWIyzkZYjQ/Sg4nVK76GHI/AAAAAAAAACE/aK5ueitr8Tk/s400/Picture+5.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5336245853146978418" /&gt;&lt;/a&gt;If you're using a built-in data view (table view, image view, etc.) as the sole item on the screen, it probably already behaves the way you want. If you're using a custom layout like this one, which includes a UITableView, controls, and buttons, you'll need to use the Size Inspector in Interface Builder. To control positioning, the Size Inspector uses the "springs and struts" metaphor: Views are locked to superviews (or not) based on the "struts" around the edge , and they expand or contract within their superview based on the "springs" on the inside of the rectangle. Click a strut or spring to enable it or disable it.&lt;br /&gt;&lt;br /&gt;The first step in reocnfiguring your view is to set up the autosizing for the main UIView that contains all your subviews. This one's easy: Just enable every strut and spring. That ensures that each edge of the view stays a fixed distance from the corresponding edge of the superview (the application window) and changes its width or height as the window resizes.&lt;br /&gt;&lt;br /&gt;I set up the UITableView in the picture the same way: Its top edge is a fixed distance from the top edge of the view (to make room for those other controls), and I want it to expand or shrink based on the containing view. It will expand to fill the width of the superview in landscape mode, and it will add height (and shrink its width) in portrait mode.&lt;br /&gt;&lt;br /&gt;I wanted the button in the upper left to stay in the upper left, regardless of the orientation. In that case I enabled the strut on the left side and the strut on the top. I disabled every other strut and every spring. That ensures that the button doesn't change its size based on the orientation and that it stays in the upper left. Not surprisingly, I set up the add button in the upper right and the Today/All control next to it to be locked to the upper right corner, again without shrinking or growing.&lt;br /&gt;&lt;br /&gt;A little bit of tinkering got all my views to look nice in either rotation. But I add my own custom views to the UIWindow object, and that is simply not working yet. The view pops out of the edge next to the home button, regardless of the phone's orientation, and it resizes in a sloppy way. When I figure that out, I'll post part 2.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-2870020752776574121?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/2870020752776574121/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/real-world-iphone-rotation-part-1.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/2870020752776574121'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/2870020752776574121'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/real-world-iphone-rotation-part-1.html' title='Real-World iPhone Rotation, Part 1'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_2GWIyzkZYjQ/Sg4kVkflShI/AAAAAAAAAB0/qPJGDiSP2Bg/s72-c/Picture+3.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-3690892857156898153</id><published>2009-05-09T14:29:00.000-07:00</published><updated>2009-05-10T09:29:01.850-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='Philosophy'/><title type='text'>Refactoring Pays Down Technical Debt</title><content type='html'>One of &lt;a href="http://programmingobsession.blogspot.com/2009/04/technical-debt.html"&gt;my first posts on this site&lt;/a&gt; mentioned technical debt, a brilliant metaphor for the way that hurrying imperfect code out the door provides short-term gains but long-term costs for new features. Enough technical debt, and your feature development slows to a crawl. One way to pay down technical debt is to refactor code: Refactoring's main role is to adapt a code base to requirements that weren't on the table when the programmers started typing.&lt;br /&gt;&lt;br /&gt;I've talked about &lt;a href="http://programmingobsession.blogspot.com/2009/04/constant-refactoring-practical-benefit.html"&gt;continual refactoring&lt;/a&gt; before, which I do as a general rule in any code I find myself in, but that is usually directionless cleanup. A deliberate refactoring phase is even better. (As long as you don't fall into the "rewrite everything" trap that &lt;a href="http://en.wikipedia.org/wiki/Netscape_6"&gt;Netscape found itself in&lt;/a&gt;, which caused such a long delay in version 6 that the company essentially handed the entire browser market to Internet Explorer.)&lt;br /&gt;&lt;br /&gt;In my iPhone app, I have three "detail" views that, when I first wrote them, had only surface-level similarities.* They had different &lt;a href="http://en.wikipedia.org/wiki/Data_access_layer"&gt;DAL&lt;/a&gt; requirements, used different objects, and so forth. But as I added new functionality to each, such as &lt;a href="http://programmingobsession.blogspot.com/2009/04/enhancing-uibutton-or-not.html"&gt;my ScheduleButton widget&lt;/a&gt; and &lt;a href="http://programmingobsession.blogspot.com/2009/04/delegation-and-redesign.html"&gt;my date picker widget&lt;/a&gt;, they began to look more alike than not. I entered a bug for myself to refactor all the common code into one base class. &lt;br /&gt;&lt;br /&gt;A week later, I did the work. There was no design requirement driving this: I just knew the code would end up in a better place at the end. I abstracted common functionality into superclass utility methods and used the &lt;a href="http://en.wikipedia.org/wiki/Template_method"&gt;template method design pattern&lt;/a&gt; to put a flow into the base class that used abstract methods ("abstract," because Objective C doesn't support this) to fill in view-specific details. &lt;br /&gt;&lt;br /&gt;A week after that, I realized I needed to change the method signature for my date picker delegate methods to support a new feature. That meant going into all the delegates and updating the code. A simple search/replace wouldn't do what I needed.&lt;br /&gt;&lt;br /&gt;Before my refactor, that fix would have affected eight methods in four classes. After my refactor, it affected four methods in two classes. I halved the development effort it took to support this new feature. But I also halved the testing effort. Since all my detail views descend from a base class that handles all the date chooser work, I only had to test one of them &amp;mdash; plus one unrelated view &amp;mdash; to ensure that all three were working. If I need to add a feature across all the detail views, it will take me one-third the time it would have before.&lt;br /&gt;&lt;br /&gt;*&lt;em&gt; As always, I should point out that my iPhone app just isn't that big. I'm unlikely to ever have a vast amount of technical debt in it. Still, it illustrates the point in a microcosm. Larger systems have proportionally more complexity and thus more opportunity for technical debt.&lt;/em&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-3690892857156898153?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/3690892857156898153/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/refactoring-pays-down-technical-debt.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/3690892857156898153'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/3690892857156898153'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/refactoring-pays-down-technical-debt.html' title='Refactoring Pays Down Technical Debt'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-8903949882440421404</id><published>2009-05-01T08:36:00.000-07:00</published><updated>2009-05-01T08:39:08.477-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Bedtime Reading'/><title type='text'>Girls On Rails</title><content type='html'>Martin Fowler &lt;a href="http://martinfowler.com/bliki/SmutOnRails.html"&gt;has a nice essay&lt;/a&gt; about a recent controversy in the Rails community: A presenter gave a talk from a slide deck garnished with scantily clad pretty women. The Rails community, he notes, eschews typical corporate values. But they can't ignore decades of professional discrimination against women.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-8903949882440421404?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/8903949882440421404/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/girls-on-rails.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/8903949882440421404'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/8903949882440421404'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/05/girls-on-rails.html' title='Girls On Rails'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-9022499033447442044</id><published>2009-04-30T12:41:00.000-07:00</published><updated>2009-04-30T23:15:18.224-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><title type='text'>Database Updates, Redux</title><content type='html'>Before I came up with my &lt;a href="http://programmingobsession.blogspot.com/2009/04/on-fly-database-updates.html"&gt;solution for updating the embedded database in my iPhone app&lt;/a&gt;, I had a different strategy that I couldn't figure out how to implement. Of course, after I had the new solution in place, I figured out how to implement my first idea.&lt;br /&gt;&lt;br /&gt;I wanted to figure out how to construct method names on the fly and invoke them. That way, I could have &lt;code&gt;dbChangeScript0&lt;/code&gt;,&lt;code&gt;dbChangeScript1&lt;/code&gt;, and so forth, and then invoke them in a while loop that ran until the application didn't have a method with the relevant name. I wanted JavaScript's &lt;a href="http://www.w3schools.com/jsref/jsref_eval.asp"&gt;&lt;code&gt;eval&lt;/code&gt; function&lt;/a&gt; in Objective C. I didn't see how to do it, though.&lt;br /&gt;&lt;br /&gt;Today, I discovered &lt;a href="http://www.cocoadev.com/index.pl?NSSelectorFromString"&gt;&lt;code&gt;NSSelectorFromString&lt;/code&gt;&lt;/a&gt;, a function that transforms a string into a selector (essentially, a function pointer). I used it to implement Plan A as follows:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;- (void) installChangeScripts {&lt;br /&gt;    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];&lt;br /&gt;&lt;br /&gt;    int versionNum = [self dbVersionNumber];&lt;br /&gt;    SEL curSelector = NSSelectorFromString([NSString stringWithFormat:@"dbChangeScript%d",versionNum + 1]);&lt;br /&gt;    while ([self respondsToSelector:curSelector]) {&lt;br /&gt;        [self performSelector:curSelector];&lt;br /&gt;        [self updateDbVersion:versionNum +1];&lt;br /&gt;        &lt;br /&gt;        versionNum = [self dbVersionNumber];&lt;br /&gt;        curSelector = NSSelectorFromString([NSString stringWithFormat:@"dbChangeScript%d",versionNum+1]);&lt;br /&gt;    }&lt;br /&gt;    [pool release];&lt;br /&gt;    &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Is this a better version? I've lost the self-documenting method names such as &lt;code&gt;makeVersionTable&lt;/code&gt;, but I've removed an in-memory array and a #define that I had to remember to update when I updated the array. In other words, I've increased the code's maintainability. (The code looks shorter, but only because I also factored out the version update into a new method.) Overall, I consider this a win.&lt;br /&gt;&lt;br /&gt;The Ruby on Rails community has a mantra: convention over configuration. In Rails, you can get a full website up and running just by following a few naming conventions &amp;mdash; ones you probably would have used anyway. A class of name X will translate to a database table named Y and so on. Compare that to Spring, a powerful Java framework that requires constant work in the XML-based configuration files.&lt;br /&gt;&lt;br /&gt;In essence, I voted for the convention side of the fence in this code. By following a consistent naming scheme, I can write a small method that will do all the work. All I have to do is add a new method with a predictable name. Compare that to an array that I had to remember to configure properly.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-9022499033447442044?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/9022499033447442044/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/database-updates-redux.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/9022499033447442044'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/9022499033447442044'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/database-updates-redux.html' title='Database Updates, Redux'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-7009084095302702828</id><published>2009-04-30T07:28:00.000-07:00</published><updated>2009-04-30T08:50:55.736-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><title type='text'>On-The-Fly Database Updates</title><content type='html'>I love the fact that the iPhone comes with an installation of &lt;a href="http://www.sqlite.org"&gt;SQLite&lt;/a&gt;*. As someone who's worked with relational databases and the SQL language for his entire professional career, I happily use it to store the information I need for my iPhone app. &lt;br /&gt;&lt;br /&gt;But with an embedded database comes the need to alter its structure on the fly as my data requirements change. I'm still in beta testing, so I &lt;em&gt;could&lt;/em&gt; tell all my testers to nuke their data the next time they install my application, but I'd prefer to avoid that, and I certainly won't tell my future customers to do that.&lt;br /&gt;&lt;br /&gt;Instead, I came up with a simple system for making the app run its own database updates. I have a class, DBAccess, that puts an object-oriented wrapper around the very procedural SQLite calls. I created a new method, installChangeScripts, that looks like this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;- (void) installChangeScripts {&lt;br /&gt;    SEL changeScripts[CHANGE_SCRIPTS];&lt;br /&gt;    &lt;br /&gt;    changeScripts[0] = @selector(makeVersionTable);&lt;br /&gt;    changeScripts[1] = @selector(addTaskDuration);&lt;br /&gt;    &lt;br /&gt;    int versionNum = [self dbVersionNumber];&lt;br /&gt;    &lt;br /&gt;    for (int changeScriptIndex = versionNum + 1; &lt;br /&gt;                changeScriptIndex &lt; CHANGE_SCRIPTS; &lt;br /&gt;                changeScriptIndex++) {&lt;br /&gt;        [self performSelector:changeScripts[changeScriptIndex]];&lt;br /&gt;        NSString *versionUpdateQuery = &lt;br /&gt;              [[NSString alloc] &lt;br /&gt;                      initWithFormat:@"update app_version set version_num = %d",&lt;br /&gt;                                                     changeScriptIndex];&lt;br /&gt;        [self executeUpdate:versionUpdateQuery];&lt;br /&gt;        [versionUpdateQuery release];&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The gist is straightforward: Make an array of method selectors (Objective C's way to find a method at runtime), iterate through them starting at the database's current version number + 1, and update the version number after the script has run. The only tricky part is that the version table doesn't exist in the default version of the database, so I have logic to handle the case where it doesn't exist:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;- (int) dbVersionNumber {&lt;br /&gt;    int versionNum = -1;&lt;br /&gt;    // get the current version. handle this case differently since it drives the rest&lt;br /&gt;    // of the logic&lt;br /&gt;    NSArray *queryResult = [self executeQuery:@"select version_num from app_version"];&lt;br /&gt;    if ([queryResult count] &gt; 0) {&lt;br /&gt;        // handle the result &lt;br /&gt;        NSDictionary *versionResult = [queryResult objectAtIndex:0];&lt;br /&gt;        versionNum = [[versionResult objectForKey:@"version_num"] intValue];&lt;br /&gt;    }&lt;br /&gt;    [queryResult release];&lt;br /&gt;    return versionNum;    &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Originally, this logic was in the &lt;code&gt;installChangeScripts&lt;/code&gt; method, but that was an easy refactor: self-contained logic that only produces a single value  used by the rest of the method. See the &lt;a href="http://www.refactoring.com/catalog/extractMethod.html"&gt;Extract Method&lt;/a&gt; refactoring sequence.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt; Not surprisingly, the first change script creates the version table.&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;- (void) makeVersionTable {&lt;br /&gt;    [self executeUpdate:@"create table app_version (version_num integer)"];&lt;br /&gt;    [self executeUpdate:@"insert into app_version values (0)"];&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;There's at least one other good refactoring target here: The setup of the list of install scripts should be in its own method because it's a separate concept from actually running the scripts. I just need to remind myself how to deal with &lt;code&gt;calloc&lt;/code&gt;, &lt;code&gt;free&lt;/code&gt;, and other denizens of the pointer/array abyss.&lt;br /&gt;&lt;br /&gt;* &lt;em&gt;I also love the group's motto, "Fast. Small. Reliable. Choose any three." A nice spin on the various "choose two" sayings such as "Fast. Scalable. Cheap. Choose two."&lt;/em&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-7009084095302702828?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/7009084095302702828/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/on-fly-database-updates.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7009084095302702828'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7009084095302702828'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/on-fly-database-updates.html' title='On-The-Fly Database Updates'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-6115970275871032329</id><published>2009-04-29T21:27:00.001-07:00</published><updated>2009-04-29T21:28:18.980-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Bedtime Reading'/><title type='text'>Old-School Coding Techniques</title><content type='html'>Computerworld &lt;a href="http://www.computerworld.com/action/article.do?command=viewArticleBasic&amp;taxonomyName=Development&amp;articleId=9132061&amp;taxonomyId=11&amp;pageNumber=1"&gt;takes a look at old-school programming techniques&lt;/a&gt; we should all be happy to forget. But what about old-school techniques that are still relevant or should be revived?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-6115970275871032329?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/6115970275871032329/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/old-school-coding-techniques.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6115970275871032329'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6115970275871032329'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/old-school-coding-techniques.html' title='Old-School Coding Techniques'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-4066806859721609124</id><published>2009-04-28T10:15:00.000-07:00</published><updated>2009-04-28T19:05:25.835-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><category scheme='http://www.blogger.com/atom/ns#' term='Philosophy'/><title type='text'>Meaty Learning Projects</title><content type='html'>My job description implicitly includes understanding current technologies, philosophies, and methodologies. Usually, this is just a surface-level knowledge about what's going on in the industry, but occasionally I decide to dig deeper. &lt;br /&gt;&lt;br /&gt;For instance, right now I'm learning Scala because I think it will solve some thorny performance issues at work. But it's a Catch-22: I don't want to introduce a whole new language without knowing if it will really benefit us, but I can't know if it will benefit us until I try it with a meaty project. And it's even worse than normal in this case: The feature I really want to explore &amp;mdash; actors &amp;mdash; requires a decent chunk of knowledge before I can take advantage of it.&lt;br /&gt;&lt;br /&gt;Over the years, I've developed a solution to this problem. I have a substantial, but not overly complicated, program that I write whenever I come to a new platform. I write a program that assists me in finding solutions to the kind of thorny word puzzles you get from &lt;a href="http://www.puzzlers.org"&gt;the National Puzzlers League&lt;/a&gt;. It requires me to open a file, read in words, and see if they match the pattern the user is searching for. No network connection. No database work. No scalability issues.&lt;br /&gt;&lt;br /&gt;It's a problem space I understand, so I can dive in without much thought. I get a deeper knowledge of the language than I'd get from working through a tutorial's abstract problems. It forces me to probe and dig through the libraries to accomplish what I need. I don't get a thorough understanding of the language, but I get pretty far.&lt;br /&gt;&lt;br /&gt;Overall, it's a plan that's worked well for me.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-4066806859721609124?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/4066806859721609124/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/meaty-learning-projects.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4066806859721609124'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4066806859721609124'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/meaty-learning-projects.html' title='Meaty Learning Projects'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-7081272658234909499</id><published>2009-04-28T08:27:00.000-07:00</published><updated>2009-04-28T08:28:47.779-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Bedtime Reading'/><title type='text'>Article On EasyMock</title><content type='html'>IBM's DeveloperWorks features &lt;a href="http://www.ibm.com/developerworks/java/library/j-easymock.html?ca=drs-"&gt;an article about EasyMock&lt;/a&gt;, a framework for building mock objects for unit testing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-7081272658234909499?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/7081272658234909499/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/article-on-easymock.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7081272658234909499'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/7081272658234909499'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/article-on-easymock.html' title='Article On EasyMock'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-6791232719992469661</id><published>2009-04-24T11:13:00.000-07:00</published><updated>2009-04-30T09:32:25.882-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='Philosophy'/><title type='text'>The Root Of All Evil</title><content type='html'>In my most recent round of interviews, I chatted with the founder of &lt;a href="http://www.u1.com"&gt;U1 Technologies&lt;/a&gt;. He and I had a lot in common philosophically and, with a slight shift in timing, I might very well have been working for him instead of Maxis.&lt;br /&gt;&lt;br /&gt;One of the things that caught my attention was his phone screen questions. I've been on both ends of these things, and they usually consist of simple questions that ensure that the candidate isn't obviously inflating his or her resume. Things like: "Explain how an Iterator works." or "When would you use a List versus a Map?" (Yes, I've actually been asked these.)&lt;br /&gt;&lt;br /&gt;Nathaniel's first question reflected his company's decision to only hire senior engineers: "When should you optimize your code?" Fifteen years of software engineering provided a quick answer: "When you know there's a performance problem with it."&lt;br /&gt;&lt;br /&gt;"Premature optimization is the root of all evil," is a quote that's been attributed to both Donald Knuth and C.A.R. Hoare. (Most people cite Knuth, who cites Hoare, who denies saying it, according to &lt;a href="http://en.wikipedia.org/wiki/Optimization_(computer_science)#When_to_optimize"&gt;Wikipedia&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;This may sound at odds with one of my other favorite programming axioms: "The earlier you find the bug, the cheaper it is to fix." But optimizing code usually goes hand in hand with making it less maintainable and reusable. Specializing algorithms, caching values, adding different control structures, and more all add complexity. If you don't have performance numbers to back up that work, you're adding the complexity and (probably) not getting any benefit from it.&lt;br /&gt;&lt;br /&gt;You may have inefficient code that rarely gets called. You may think something's inefficient when in fact the compiler or optimizer or processor does just fine with it. Or your code could actually be inefficient and it might just not matter enough to affect the user.&lt;br /&gt;&lt;br /&gt;I've seen this in practice for years. And yet the temptation to optimize is always there, coaxing even the best programmers into a fen of tangles and mazes and confusion.&lt;br /&gt;&lt;br /&gt;While I was working on the "real time time update" feature of &lt;a href="http://programmingobsession.blogspot.com/2009/04/delegation-and-redesign.html"&gt;my reusable date picker pop-up&lt;/a&gt;, I set it up so that the delegate would be called for every UIControlEventValueChanged message from the UIDatePicker.&lt;br /&gt;&lt;br /&gt;I envisioned the date and time changing as the wheels in the date picker spun to their new locations, so I decided to be smart about calling the delegate method.&lt;br /&gt;&lt;br /&gt;I could have written this in my event-handling method:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;    if ([delegate respondsToSelector:@selector(dateChangedWithoutSaving:)]) {&lt;br /&gt;           [delegate dateChangedWithoutSaving: date];&lt;br /&gt;   }&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;But I assumed that &lt;code&gt;respondsToSelector&lt;/code&gt; would be a relatively expensive call. I also assumed that the runtime binding to &lt;code&gt;dateChangedWithoutSaving:&lt;/code&gt; method would be costly. And I didn't want that cost slowing down the user's interaction with the date picker wheels. Instead, I made a boolean in the controller that tracked whether or not the delegate actually supported the method and set it at the same time I stored the reference to the delegate:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;    - (void) setDelegate: (id) delegate {&lt;br /&gt;         ...&lt;br /&gt;&lt;br /&gt;          delegateRespondsToContinuousDateChanged = [delegate respondsToSelector:@selector(dateChangedWithoutSaving:)];&lt;br /&gt;    }&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;I changed my event-handling logic accordingly:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;    if (delegateRespondsToContinuousDateChanged) {&lt;br /&gt;        [delegate dateChangedWithoutSaving:[myDatePicker date]];&lt;br /&gt;    }&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then I fired up my app.&lt;br /&gt;&lt;br /&gt;And discovered something I didn't know. That UIControlEventValueChanged message only gets sent once the date picker wheels have finished spinning. Rather than sending a message for every number that cruises by, it only sends, essentially, one message per gesture. My optimization didn't buy me much, since the code can't possibly interfere with the spinning wheel animation.&lt;br /&gt;&lt;br /&gt;So what did my "clever" optimization get me?&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;My own implementation of &lt;code&gt;setDelegate&lt;/code&gt; (rather than the one created by Objective C's &lt;code&gt;synthesize&lt;/code&gt; keyword)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;An extra layer of logic in my event-handling code.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;I've added more complexity (the extra layer of logic) and more lines of code (the &lt;code&gt;setDelegate&lt;/code&gt; method) with the concurrent possibility of bugs.* Performance gain: virtually none.&lt;br /&gt;&lt;br /&gt;Premature optimization is the root of all evil.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;* Yes, of course, this isn't much extra complexity or code. But add up enough items like this, and you get a significant amount of &lt;a href="http://programmingobsession.blogspot.com/2009/04/technical-debt.html"&gt;technical debt&lt;/a&gt;&lt;/em&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-6791232719992469661?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/6791232719992469661/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/root-of-all-evil.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6791232719992469661'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/6791232719992469661'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/root-of-all-evil.html' title='The Root Of All Evil'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-5524165846931256064</id><published>2009-04-22T23:19:00.000-07:00</published><updated>2009-04-24T08:39:48.353-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='Patterns'/><title type='text'>Delegation And Redesign</title><content type='html'>One side effect of programming for the iPhone (or Mac OS X) is a heightened awareness of the &lt;a href="http://en.wikipedia.org/wiki/Delegation_pattern"&gt;delegation pattern&lt;/a&gt;. It seems like every class in the framework has delegate information.&lt;br /&gt;&lt;br /&gt;When I started my UI redesign work in my iPhone app, I saw a perfect opportunity for implementing my own delegate pattern.&lt;br /&gt;&lt;br /&gt;The idea behind delegation is straightforward. Object A holds a reference to Object B. Object A goes about its merry way performing its duties, except that every now and then it says to Object B, "Hey, I need you to handle this, okay?" To everyone else, it looks like Object A did the work. A good example is the UITableview in the iPhone framework. It supports two delegates, one for supplying the table's data, one for handling the table's behavior. The data delegate has methods such as numberOfSectionsInTableView; the behavior delegate has methods such as tableView:didSelectRowAtPath:. The table view doesn't care about anything else those objects might do: It just expects them to do what it needs.&lt;br /&gt;&lt;br /&gt;The pattern encourages code reuse (I have a single instance of a delegate that handles common behavior for all of my text fields) and separates business logic from other layers (I have specialized objects that act as my tableview data sources, leveraging a model object in different ways, all without modifying UITableView in any way). It lets you make complex object compositions from subsystems. It makes breakfast for you in the morning. It's great. (As with all general-purpose design patterns, you may need to consider performance and threading issues when putting it into practice.)&lt;br /&gt;&lt;br /&gt;In Objective C, it's easy to use a delegate:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;       id delegate; // set by an outside client&lt;br /&gt;      [delegate invokeDelegateMethod]&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Even though the compiler doesn't know if &lt;code&gt;delegate&lt;/code&gt; implements &lt;code&gt;invokeDelegateMethod&lt;/code&gt;, it compiles this code with only a warning. (id is shorthand for "an otherwise untyped object.") Of course, if you try and run this, and &lt;code&gt;delegate&lt;/code&gt; does not implement &lt;code&gt;invokeDelegateMethod&lt;/code&gt;, you'll get a runtime exception. This may be what you want, but you can also use:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;     id delegate;&lt;br /&gt;     if ([delegate respondsToSelector:@selector(invokeDelegateMethod)]) {&lt;br /&gt;         [delegate invokeDelegateMethod];&lt;br /&gt;     }&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This makes the method optional. You can be formal and specify a protocol that the object must declare to get yourself some type safety, but it's not strictly necessary. Java has similar "rummage around in an object's belly and see what it contains" tools in its reflection API, so you could easily hook up delegate patterns the same way in that language.&lt;br /&gt;&lt;br /&gt;For my redesign, I had several places where I needed to pop up a view that had a UIDatePicker, a Done button, and a Cancel button. The idea being that the user needs to pick a date and tell me when s/he's done. The delegate pattern seemed to be the perfect approach, since I had one class that I needed to reuse throughout the application and have it fit seamlessly into the flow of each area.&lt;br /&gt;&lt;br /&gt;In my first pass, I implemented two delegate methods, &lt;code&gt;dateSaved: (NSDate *)date&lt;/code&gt; and &lt;code&gt;datePickerViewDismissed&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;I made the first one optional: Why should the date choosing view care whether the client &lt;em&gt;actually&lt;/em&gt; wants to know about the date that was chosen? I made the second method required (i.e., I don't check to see if the delegate supports the method before calling) because that's the method that triggers the client to release any memory it used for the view. I wanted to make sure that I'd know right away if I forgot to implement that method.&lt;br /&gt;&lt;br /&gt;I wired the view into the first use case: A screen that shows one date at a time would let the user jump to any arbitrary day, rather than nexting and previousing a bunch. Everything worked fine, so I started to wire it into the next part of the app, where you set the time for a single task. That's when I had the eye candy idea for my &lt;a href="http://programmingobsession.blogspot.com/2009/04/enhancing-uibutton-or-not.html"&gt;ScheduleButton&lt;/a&gt;. With a bit more work, I could make a delegate method for the date picker view that would fire as the date changed, which meant that I could update the button's text as the user spun the wheels.&lt;br /&gt;&lt;br /&gt;My new version added a new delegate method, &lt;code&gt;dateChangedWithoutSaving: (NSDate *)date&lt;/code&gt;. But what if the user cancels? My architecture didn't support that, so I changed the &lt;code&gt;datePickerViewDismissed&lt;/code&gt; method into &lt;code&gt;datePickerViewDismissedWithButton: (DateChooserButton)btn&lt;/code&gt; (DateChooserButton being an enum representing the two choices). Now a client could grab a copy of the date, bring up the view, update the ScheduleButton helper object as the date changed, and then reset it to the original date if the user pressed cancel.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-5524165846931256064?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/5524165846931256064/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/delegation-and-redesign.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/5524165846931256064'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/5524165846931256064'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/delegation-and-redesign.html' title='Delegation And Redesign'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-4399334708458982441</id><published>2009-04-21T19:52:00.000-07:00</published><updated>2009-04-21T19:52:00.384-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><title type='text'>Enhancing UIButton. Or Not.</title><content type='html'>As part of a UI redesign in my iPhone app, I realized I had a few concepts that would be repeated in various places, so of course I wanted to bundle them into reusable components.&lt;br /&gt;&lt;br /&gt;One idea was a button whose text would always read "&amp;lt;some fixed prefix&amp;gt;: &amp;lt;some mutable date&amp;gt;." The date changes as the containing view is visible, but the prefix is fixed early in the button's lifecycle. &lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_2GWIyzkZYjQ/Se5-kmY1VZI/AAAAAAAAABs/nCBE_RtD4dk/s1600-h/Picture+1.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 302px; height: 52px;" src="http://4.bp.blogspot.com/_2GWIyzkZYjQ/Se5-kmY1VZI/AAAAAAAAABs/nCBE_RtD4dk/s400/Picture+1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5327334576470971794" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I made a new class, called ScheduleButton, that subclassed UIButton. I added methods for setting the date format, the actual date, and the prefix. These call down into a single "updateButtonText" method that constructs the appropriate string and would have used the inherited UIButton methods to change the text.&lt;br /&gt;&lt;br /&gt;So far, so good.&lt;br /&gt;&lt;br /&gt;I dragged a UIButton object from Interface Builder into my app, and set the class to ScheduleButton (instead of UIButton, the default). I launched my app and saw the button,but without a border.&lt;br /&gt;&lt;br /&gt;I could probably figure out the reason for this. I suspect it has something to do with the fact that UIButtons are made with a class method, buttonForType, rather than a normal alloc/initWithFrame flow (at least from a client's perspective: I imagine initWithFrame gets called in there somewhere).&lt;br /&gt;&lt;br /&gt;But after hitting my head against several other issues during this redesign, I pretty much just said, "Fuck it." (Did I mention that I'd swear on this blog? Well, I will.) Instead, I converted ScheduleButton into a helper class that holds a reference to a UIButton. The controller still interacts with ScheduleButton, using the same methods, and they feed into one centralized method, but that one method now invokes setTitle on the contained UIButton rather than inheriting it. That means that other objects can muck with the UIButton on their own, without my helper being the wiser, though that's not likely to be a problem since I'm the sole user. (There's probably yet another solution involving &lt;a href="http://en.wikipedia.org/wiki/Objective-C#Categories"&gt;Objective-C's category functionality&lt;/a&gt;, but I don't want to modify every instance of the UIButton class in my application: I just want to modify particular objects.)&lt;br /&gt;&lt;br /&gt;I tried to lend credence to this implementation by convincing myself that this was an instance of the &lt;a href="http://en.wikipedia.org/wiki/Decorator_pattern"&gt;Decorator design pattern&lt;/a&gt;, but it's pretty clearly not. A proper Decorator shares a parent with the decorated object and implements every method by calling into the (completely contained) decorated object. It then adds extra functionality that the decorated object doesn't know about. &lt;br /&gt;&lt;br /&gt;Ah, well. Sometimes you just need something to work.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-4399334708458982441?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/4399334708458982441/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/enhancing-uibutton-or-not.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4399334708458982441'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4399334708458982441'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/enhancing-uibutton-or-not.html' title='Enhancing UIButton. Or Not.'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_2GWIyzkZYjQ/Se5-kmY1VZI/AAAAAAAAABs/nCBE_RtD4dk/s72-c/Picture+1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-576626806214564608</id><published>2009-04-21T12:01:00.000-07:00</published><updated>2009-04-21T12:27:16.205-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Bedtime Reading'/><title type='text'>Evolutionary Architecture and Emergent Design</title><content type='html'>IBM's DeveloperWorks has &lt;a href="http://www.ibm.com/developerworks/views/java/libraryview.jsp?search_by=evolutionary+architecture+emergent+design"&gt;an interesting series&lt;/a&gt; on finding the design inherent in systems through test-driven development and other strategies.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-576626806214564608?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/576626806214564608/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/evolutionary-architecture-and-emergent.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/576626806214564608'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/576626806214564608'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/evolutionary-architecture-and-emergent.html' title='Evolutionary Architecture and Emergent Design'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-4077952622483723325</id><published>2009-04-19T14:14:00.000-07:00</published><updated>2009-04-19T17:27:57.986-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='Philosophy'/><title type='text'>Constant Refactoring: A Practical Benefit</title><content type='html'>As I program, I continually refactor the code I'm in. It's habit at this point. I rarely get the opportunity to do a massive change, but even the basic work I do &amp;mdash; removing duplicated code, generalizing logic, dividing up large functions, and so on &amp;mdash; reaps unexpected benefits.&lt;br /&gt;&lt;br /&gt;Refactoring partly makes code more maintainable, but its main purpose is to make the code flexible enough to respond to new design requirements. I try to code on the assumption that change will come, but if I could predict the exact direction of every future change and thus craft a perfect design, I'd be writing this from my French Chateau.&lt;br /&gt;&lt;br /&gt;Here's a simple example I recently found in my iPhone app. &lt;br /&gt;&lt;br /&gt;One of the tabs lets you move between dates, one at a time, to see your schedule for that particular date. In my initial design, I didn't let you change dates because that view exists to show you what's going on today. But I quickly realized that I wanted to at least see what tomorrow would bring.&lt;br /&gt;&lt;br /&gt;When I made that change, I had two buttons that would switch to either the next date or the previous date. They each called separate methods &amp;mdash; nextDay and previousDay &amp;mdash; that were fairly similar to each other. I was working through the basic functionality, so I didn't register the commonality. I think this is common enough: You're so focused on one thing that you don't notice something else.&lt;br /&gt;&lt;br /&gt;On a later pass through the code, fixing something else, I realized how similar the methods were, and I wrote a new method that would just adjust the date based on some time interval. I gutted nextDay and previousDay and replaced them with calls to the new method that passed either 86400 or -86400. (The number of seconds in a day.)&lt;br /&gt;&lt;br /&gt;After that, one of my testers suggested that I let the user swipe across the date to move to the next or previous day, depending on the direction. Once I figured out the gesture system for the iPhone, I just made each swipe call either of the two appropriate methods, which in turn called into that common method.&lt;br /&gt;&lt;br /&gt;But then I realized that I needed to add animation to show the user what was happening. I put the animation logic into the generalized method and made it smart enough to know that a negative number meant animate one way, while a positive number meant animate the other way (animate from the left in the first case, because you're moving to the previous day, and animate from the right in the second). That handled the animation for the two cases.&lt;br /&gt;&lt;br /&gt;A couple of my testers asked for the ability to jump to an arbitrary day. I decided to look into implementing that this morning, and once I opened the code, I realized it would be a snap. I bring up a UIDatePicker, the "choose from a list" UI in the iPhone, and then just pass the time interval between the user-selected date and the current date to that one common method. The logic is the same, and the code animates appropriately: If you jump to some arbitrary, far-future date, the screen wipes from right to left, as you'd expect. Aside from bringing up the date picker, I wrote exactly one line of code to enable the feature.&lt;br /&gt;&lt;br /&gt;For this simple example, I probably would have found my way to the same end result: an all-purpose method for jumping to any given date. But the fact that I had refactored earlier &amp;mdash; just because &amp;mdash; made the solution obvious and quick.&lt;br /&gt;&lt;br /&gt;When I started this feature, you couldn't switch dates at all. Piece by piece, I built the infrastructure so that now you can jump to any date with gestures and animations. That wasn't the original design, but constant refactoring allowed me to eventually support this radically new direction with a minimum of "from scratch" code.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-4077952622483723325?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/4077952622483723325/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/constant-refactoring-practical-benefit.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4077952622483723325'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/4077952622483723325'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/constant-refactoring-practical-benefit.html' title='Constant Refactoring: A Practical Benefit'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-1826757986900549477</id><published>2009-04-19T09:20:00.000-07:00</published><updated>2009-04-19T11:14:28.633-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><title type='text'>iPhone Mail Reformatting</title><content type='html'>I recently wired up the ability to email lists from an iPhone app I'm writing. Setting this up was pretty straightforward (see &lt;a href="http://developer.apple.com/iPhone/library/documentation/ContactData/Conceptual/AddressBookProgrammingGuideforiPhone/100-Introduction/Introduction.html"&gt;the Apple docs&lt;/a&gt; on the subject).&lt;br /&gt;&lt;br /&gt;But since I'm sending lists, and since my list might have headings with one level of subitems, I wanted to create an email that looked like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Heading&lt;br /&gt;   Item 1&lt;br /&gt;   Item 2&lt;br /&gt;   Item 3&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I added the appropriate spaces to my email body, URL encoded it with NSString's stringByAddingPercentEscapesUsingEncoding method, and shipped it off to Mail with [[UIApplicaton sharedApplication] openUrl:]&lt;br /&gt;&lt;br /&gt;What I saw when Mail started was this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Heading&lt;br /&gt;Item 1&lt;br /&gt;Item 2&lt;br /&gt;Item 3&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I assumed my indent code didn't work, so I looked at the result in the debugger. The encoded string had the three spaces in front of each item; Mail had decided it didn't like the spaces at the beginning of the line. I tried using a tab instead. No dice.&lt;br /&gt;&lt;br /&gt;Thinking that I could trick Mail by putting a different character at the beginning, I sent it email that should have looked like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Heading&lt;br /&gt;-   Item 1&lt;br /&gt;-   Item 2&lt;br /&gt;-   Item 3&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;What I got was:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Heading&lt;br /&gt;- Item 1&lt;br /&gt;- Item 2&lt;br /&gt;- Item 3&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Mail not only doesn't like white space at the beginning of the line; it doesn't like multiple spaces at all. &lt;br /&gt;&lt;br /&gt;I find this very annoying. I'm sending Mail a piece of text that I want it to use, and it's making its own decisions about that text. Mail would let me type "   Item 1"; it just won't take it from a passed-in URL.&lt;br /&gt;&lt;br /&gt;Grr. In the end, I decided to just live with the last version of the list. A friend suggested "--Item 1" but I don't like the way the dashes run into the text in that form. Maybe "-- Item 1."&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-1826757986900549477?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/1826757986900549477/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/iphone-mail-reformatting.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/1826757986900549477'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/1826757986900549477'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/iphone-mail-reformatting.html' title='iPhone Mail Reformatting'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-2176726939419313683</id><published>2009-04-19T08:02:00.000-07:00</published><updated>2009-04-19T08:26:06.247-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Philosophy'/><title type='text'>Technical Debt</title><content type='html'>Some time in the last year, I learned the term &lt;em&gt;&lt;a href="http://en.wikipedia.org/wiki/Technical_debt"&gt;Technical Debt&lt;/em&gt;&lt;/a&gt;. It's a brilliant metaphor for the more hurried, less-thought-out code that crops up in any project with a ship date. &lt;br /&gt;&lt;br /&gt;The idea is simple: Just as you actively take on financial debt to give yourself new opportunities, you take on technical debt to ship a product out the door and make money from it. Financial debt is simple money, but technical debt is more complex. Any decision you make to ship the product at the expense of a clean code base qualifies. Every test you don't write is technical debt. Every piece of duplicated code that hasn't been refactored into common functionality is technical debt. Every todo comment is technical debt. Every bug you ship with is technical debt. &lt;br /&gt;&lt;br /&gt;But too much technical debt, and you never pay down the principal, in this case the ability to quickly add new features or fix old bugs. That duplicated code? If you never refactor it, every bug fix in that code has a cost proportional to the number of places it's used. And that's if you actually remember where all those pieces are. That todo comment you never go back to? Some day you'll need to enhance the code around it, and that todo will add to the cost of the work. Add enough of these little debts, and the cost for any given feature multiplies.&lt;br /&gt;&lt;br /&gt;Unfortunately, while it's easy to measure financial debt, it's tough to measure technical debt, which makes it difficult to use when arguing for time to fix things up in the code base. If you never need to fix a bug in that duplicated code, for instance, you'll never experience the extra cost of fixing it in every location. Of course, the idea that a chunk of code will sit forever untouched once it's written is ludicrous, but you can't sell the idea that something might be a problem someday outside of development teams. &lt;br /&gt;&lt;br /&gt;There's also no good way to flag technical debt. Perhaps as programmers we need a more formal way &amp;mdash; beyond todo comments &amp;mdash; to note down that we made a deliberate choice to take on technical debt. But then we have to advertise to the larger world that we deliberately did something not in the best possible way. I know that won't sell.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-2176726939419313683?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/2176726939419313683/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/technical-debt.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/2176726939419313683'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/2176726939419313683'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/technical-debt.html' title='Technical Debt'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9109010163420737861.post-479681617831329273</id><published>2009-04-19T07:53:00.001-07:00</published><updated>2009-04-19T08:00:57.837-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Meta'/><title type='text'>About This Blog</title><content type='html'>I'm a programmer, both professionally and passionately. This blog will contain all my thoughts, explorations, and ramblings about my programming antics.&lt;br /&gt;&lt;br /&gt;For the most part, this blog will cover my personal programming projects, but work topics will sneak in from time to time if they don't have any confidential information.&lt;br /&gt;&lt;br /&gt;Most people would call me a Java programmer, but that's just how my career has shaken out. I consider myself an "all-purpose" programmer. The topic itself intrigues me, regardless of the particulars of any one language.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9109010163420737861-479681617831329273?l=programmingobsession.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://programmingobsession.blogspot.com/feeds/479681617831329273/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/about-this-blog.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/479681617831329273'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9109010163420737861/posts/default/479681617831329273'/><link rel='alternate' type='text/html' href='http://programmingobsession.blogspot.com/2009/04/about-this-blog.html' title='About This Blog'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
