Monday, August 29, 2011

Simple Property Model in Ruby

I've talked before about how JavaScript follows what Steve Yegge calls the Universal Design Pattern. I can see the value of associating an arbitrary number of properties with an object, and to be able to say that one object is "like" another object the way JavaScript's prototype property works. I even have ideas for using this. However, I don't do my server side programming in JavasScript. Here is my first cut at a very bare bones property model using Ruby.

Code
First I'll show you the class that stores a property. It is a just a simple key/value pair.
class Property
  attr_accessor :name, :value
  
  def initialize(name, value)
    @name = name
    @value = value
  end
end
Next, is my code for the Object class (which I call Thing):
class Thing
 
  attr_accessor :prototype, :type, :properties

  def initialize
    @properties = {}
  end
 
  def self_keys
    @properties.keys
  end
 
  def keys
    self_keys | (@prototype ? @prototype.keys : [])
  end
 
  def [](key)
    return @properties[key].value if @properties.has_key? key
    @prototype ? @prototype[key] : nil
  end
 
  def []=(key, value)
    if @properties.has_key? key
      @properties[key].value = value
    else
      @properties[key] = Property.new(key, value)
    end
  end
 
  def to_hash
    hash = @prototype ? @prototype.to_hash : {}
    @properties.each_value {|p| hash[p.name] = p.value}
    hash
  end
 
  def method_missing(method, *args)
    name = method.to_s
    if (name.end_with?('='))
      self[name[0..-2]] = args[0]
    else
      return self[name]
    end
  end
end
As you can see a "Thing" consists of a type, a prototype object, and a map of key/property pairs. Property lookups (which can be done using either array type syntax or the dot syntax of methods and fields) first look at a thing's properties, and then at its prototype's properties. All in all, it is a fairly simple pair of classes.

Thoughts
There are a couple of decisions here that seem odd, so I'll explain my thinking.

Thing
Why did I call the Object type Thing? Well there is already an Object class in Ruby and I didn't want to worry about naming collisions. Like object, "thing" is a generic enough word that if I want to create a class with that name, I should probably rethink my design. So it satisfies the qualifications of describing "something" without causing any likely naming collisions.

Property
Why do I have a Property class rather than doing the more straightforward thing of having Thing just containing a Hash of key/values? Well, this is an example of me violating the YAGNI principle. I am going to want to persist this information to a database so I am envisioning a Thing table and a Property table. My assumption is that when I do that, I'll want the key/value pairs broken out into their own Property class. So I am (perhaps foolishly) breaking that out ahead of time.

method_missing
Like JavaScript, I want to be able access properties via either array syntax (thing['property']) or method syntax (thing.property). Ruby provides the functionality to handle unknown method names, via the method_missing method. As you can see, I wrote a bare bones implementation that does no error checking. If it gets passed a method that ends with '=', it assumes that it is a property assignment, otherwise it assumes it is a property read. I suppose in the future I could/should add error checking like that there are the right number of arguments to the method. I should probably also implement respond_to so it will return true for any property that exists.

Lack of Functionality
Do these classes actually buy me anything? I'm not sure. I am sure that I am going to need a bunch of additional functionality (like database persistance) before they are really useful to me. But I have to start somewhere. So rather than blog about a fully baked solution, I've decided to show you some of the raw ingredients as I go. I am hoping that this gives me a good starting point.

Monday, August 22, 2011

Wrong is Better Than Vague

Vague
I've got this idea for the next killer app. It's this game, you see, that'll leverage the unique properties of tablets. Do you want to work on implementing it with me?

What's the game?  Oh, something fun.
So this will be for the iPad? That and other tablets.
Who's going to play it?  Everyone.

Wrong
I am going to create a 3D minesweeper for the iPad and I'll sell it for $473.26 per copy. Minesweeper is already popular on the Windows, and there is this huge market of iPad owners who have already shown they are willing to pay a lot for gadgets. And with the 3D angle we can get rich off of this. Do you want to work on it with me?

The Difference
The problem with vague is that most ideas sound either good or bad depending on the listener's optimism. Vague is how a small project turns into a years long boondoggle, or how the next billion dollar idea gets missed.

In my experience most new ideas from management types (and a disturbing number of research scientists) are of the vague variety. They have these great pie in the sky ideas, but if you try to actually evaluate them for engineering or business practicality, you can't. Without details there is no way to determine if the idea is good or completely infeasible.

Take the vague example above. Would you work on it? Is it going to be successful? Who knows. What about the wrong example? Of course you wouldn't work on it, as is. But because it is concrete, it could be improved.

Nobody pays $400 for apps!   Oh, then I guess we can charge $4.95. 
The iPad doesn't support 3D.   Hmm.. then maybe we should target those cell phones that do. 
Minesweeper really doesn't lend itself to 3D.    Well, what about this other [fill in specific details here] game? 
How much work will this be?   Oh, given those details it'll take about 3 months. 
So how many do have to sell to make it worthwhile?    Oh, my time is cheap, probably just 10 copies. 
Then you'll probably be successful, but I don't think I'll help you.
As you can see, with concreteness, even wrong concreteness, decisions can be made in an informed way.

Even more important than concrete plans, are concrete implementations. There's a reason people create research prototypes, whether it is for a physical product or software. You can learn so much more from what goes wrong than you can from just the high level ideas.

So the next time you have an idea, make it concrete. A wrong implementation is worth much more than just the vague idea itself.

Monday, August 15, 2011

Emailing Server Errors

I've talked about checking the server's health, but its even nicer if the server lets you know if it has a problem. In particular, you don't want user's experiencing errors and for you to remain in blissful ignorance. A simple fix is to have your server email you whenever a user encounters an error. Here is how I did this in rails.

Catch the Error
The first thing to do is to capture any exceptions that get thrown. This can be done easily by adding the following line to your ApplicationController.
unless Rails.application.config.consider_all_requests_local # don't rescue on local testing
  rescue_from Exception, :with => :handle_error
end
As of this writing, apparently Rail 3 doesn't support 404 errors this way, so a fix for this is to add a catch-all route at the bottom of your config/routes.rb that looks something like:
# this must be the LAST rule in the file
# it allows our custom 404 error to execute
match '*path', :to =>'application#routing'
Handle the Error
Now that I have caught unhandled exceptions and 404 errors, you need to do something with them. These methods in the the ApplicationController are the ones called from the above code:
def routing(exception = nil)
  record_error "404 error", exception
  render :template => "/errors/404.html", :status => 404
end
 
def handle_error(exception)
  msg = exception ? exception.message : "unknown error"
  record_error(msg, exception)
  render :template => "/errors/500.html", :status => 500
end

def record_error(msg, exception)
  return nil if Rails.application.config.consider_all_requests_local # don't email during local testing

  referrer = request.env['HTTP_REFERER']
  url = request.url
  app_name = Rails.application.class.to_s
  user = current_user
  name = user ? user.username : "UNKNOWN"
   
  error_msg = msg
  if exception
    error_msg += " #{exception.message}\n#{exception.backtrace.join '\n'}"
  end
   
  ip = request.remote_ip
  remote_ip = request.env['HTTP_X_FORWARDED_FOR']

  logger.warn "Error #{error_msg} for url [#{url}] coming from url [#{referrer}] from user [#{user}] at ip [#{ip}] forwarded from ip [#{remote_ip}]"
   
  mail = ErrorMailer.log_error(msg, app_name, url, referrer, ip, remote_ip, name, exception)
  mail.deliver
end
You can make the 404 and 500 error pages that these methods render be whatever you like, though it is suggested that you make them useful. The record_error method that these methods calls, collect a bunch of information about the request, log them (in case mailing fails), and then emails them. The emailing is done using Rails mailing capability. Since the actual configuration for talking to the mail server will depend on your situation, you should read the documentation for that.

Emailer
To send the email, I created mailers/error_mailer.rb which is analogous to a controller. All it does is package all the variables into instance variables that the view will have:
class ErrorMailer < ActionMailer::Base
  default :from => "myApplication@myurl.com"
  helper :application

  def log_error(subject, app_name, url, referrer, ip, remote_ip, user_name, error)
    @app_name = app_name
    @url = url
    @referrer = referrer
    @ip = ip
    @remote_ip = remote_ip
    @user_error = user_error
    @user_name = user_name
    @error = error
    mail :to => "myAddress@somewhere.com", :subject=>"RAILS ERROR in #{@app_name}: #{subject}"
  end
end
The view (views/error_mailer/log_error.text.haml) turns this information into an email message:
There was an error in app #{@app_name} at time #{Time.now}.

URL:  #{@url}
Referrer:  #{@referrer}
IP:  #{@ip}
IP via a portal:  #{@remote_ip}
User: #{@user_name}
 
- if @error
  Error class: #{@error.class}
  Error message: #{@error.message}
  Error Stack Trace:
  - @error.backtrace.each do |line|
    = line
- else
  No exception to log
Warning
I do have one piece of advice: Make sure the subject you give your error emails is something very distinct that you can write mail filter rules. After I deployed a similar solution at work, the security teams scanner found my app and proceeded to run through its entire litany of tests against the server. I came in the next morning to more than 4,000 emails from 404 errors. The fact that I had put the entire development team as the recipients of these emails was just the icing on the cake.

Maybe emailing every error isn't great, since you can look in the log files for the same information. However, I find the immediacy and in your face nature of an email is good for making sure that errors get fixed and aren't just silently experienced by your users (and ex users). However, be aware that if something goes wrong, you have the potential to receive a lot of email. (not to mention choice comments from anyone else who is also getting those emails).

Monday, August 8, 2011

One year of regular posts

Though I started this blog years ago, it was one year ago that I started posting regularly. This was partially inspired by Jeff Atwood's advice to post regularly. I have no idea how he regularly posted 6 times a week given that I have a hard time posting once a week. And I think that Syd (a commenter on that post) hits the nail on the head that there is more to being a popular blogger than just being reliable. However, being reliable is definitely a prerequisite.

So how have I done? Well, I missed two weeks in the past year. And I feel like I am struggling each week to keep up. However, Google Analytics shows that I've been at least a little bit successful. I've trended up pretty consistently since I started posting regularly.


100 page views a week is far from a popular blog. But it is a number that is high enough that I know that it isn't just me and my closest friends reading my blog. So that is some measure of success.

If you've been following my blog you've noticed that I have two different types of posts, my philosophical posts and my practical/tutorial posts. When I started this blog, my goal was to post primarily philosophical posts and to generate a discussion on each post. My secret goal was to learn more from my comments than I put into my posts. Unfortunately, so far, I don't get many comments, so that isn't working out. At some point I decided to also write tutorial type posts describing how I solved a specific problem. I've learned much from others who have posted what they know, so I feel I should give back. Based on Google Analytics, the majority of people who see my blog come to view these posts. Unfortunately, if the readers don't leave comments, I have no idea if what I posted was useful or just a waste of time. I can only hope that some people are finding it useful. Even more, I hope that some of these people become regular readers (and commenters) on my more philosophical posts. I am the eternal optimist and still hope to achieve my original goal.

Of course, to accomplish that, I need to continue Jeff Atwood's advice and post regularly. While I have plenty of ideas, it is somewhat daunting to me to actually turn those ideas into posts each week. Well, here's hoping that next August I get to bore you all with another post like this.

Monday, August 1, 2011

Documentation and Context

A few weeks ago Scott Adams (of Dilbert fame) wrote a blog post about the context of a message. One of his points was that the context that a message is presented in affects how the message is received. Saying the same words at a standup comedy club and at a funeral won't have the same affect. This is related to a thought I've long had about software design documentation. We all have a context in our mind, but that of a reader is probably different.

A specific example of this difference came up at work recently. Asok, Alice, and I (not their real names) were discussing a new feature we wanted added. The feature was something Alice and I needed to support our project, but it required a change in Asok's code. Over the course of an hour long meeting Alice and I kept suggesting changes to Asok's code and he kept telling us that what we were suggesting didn't make any sense.

So why did Asok keep telling us our suggestions made no sense? It wasn't arrogance, he was right. Our suggestions didn't make sense in the context of his code. It was a lack of understanding on Alice and my's part. We were looking at database and classes, and based on their names we were making certain assumption that turned out to be wrong.

Why didn't Alice and I understand Asok's design well enough to make good suggestions? We both have plenty of experience in the field, so it wasn't that. It would be easy to blame poor naming of databases and classes on Asok's part, but now that I understand it, I'm not sure I can come up with anything better. Really it was just a matter of context. Asok had worked on this system for months, and everything fit into a model he had in his head. Alice and I didn't have that model, so we made up our own. As often happens, the model we assumed didn't match reality.

How should [we] document our designsThis, to me, is the big problem of software design documentation. I have been on both sides of this problem. I have seen people completely mangle my code because they didn't see the "correct" ways and places to extend it. I have also mangled others people's code for the same reason. This is how unmaintainable behemoths get created. The challenge is: how should Asok, Alice, or I document our designs such that the context we have in our head is conveyed to the next person?

Some people say that the code is the documentation. While the code is the reality of today, the context of the code shows how to extend, modify, and use that code. Sharing this context from the code writer's mind is crucial to code being able to be maintained beyond the original author.

So, I guess this is another one of those posts where I point out a problem and have no solution. Do you have a solution?