raise "hell": Better Programming Through Error Messages
13 Sep 2012Exceptions suck. When you are getting started on a project there is nothing worse than clicking that button, hitting enter, and then watching your software fall flat on its face. It might sound odd - then - if I were to say, more errors in your life could be a good thing.
For the past few months I’ve been working with new programmers at the University of Texas and through Rails Girls. It’s humbling to say the least, to watch a new user fumble around with Ruby and Rails. They run into problems you’ve either long forgotten about, or are so used to, you’re numb to them. After having to tell the 15th-thousand student to migrate their database to get rid of a method_missing
error I thought to myself “there has to be a better way”. Essentially our system knows exactly what the problem is, it knows that you’ve got pending migrations so how can we let a programming language talk to its users? It would be so simple if Rails could just jump out, call us on the phone and tell us to “migrate our database to fix this error”, but sadly VOIP and speech generation aren’t standard libraries (yet). Since that option is out the door, how can we talk to programmers new and old alike and tell them this important message. It’s pretty simple, we just raise a little hell.
Previously if you added an admin column to your user table via a migration and forgot to run it then refreshed a page you would still get an error, but the error message was misleading at best:
"NoMethodError: undefined method `admin?' for #<User:0x007ff1d4bfa018>""
It tells us that there is an issue with User, but doesn’t give us any clues why exactly that method is undefined. With my patch a middleware checks for pending migrations in development mode and we can raise a much more useful error:
"Migrations are pending run 'bundle exec rake db:migrate RAILS_ENV=development' to resolve the issue"
Now when I get this error I don’t cringe or rage post on stack overflow, I take delight knowing that the framework I’m using is gently coaxing me back in between the lines.
I’ll give you another example, what do you think about this error:
"undefined method `model_name' for NilClass:Class"
Not very helpful. Turns out that’s the error you get when you accidentally pass a nil object to a form_for:
<%= form_for @user do |f| %>
# ...
I’ve seen that error so many times, I can spot it from across the room. The thing that gets me is that Rails knows you’re getting into an exceptional situation as soon as you pass in a nill or empty object into form_for
but it wasn’t checking, instead it just passed whatever bad arguments you passed in around until something errored out somewhere in the call stack. So now we’ve got a bad error message and a stack trace that leads us down a rabbit hole, can we fix it? Sure, a simple object.blank?
check and we can raise a meaningful error message:
raise "First argument in form cannot contain nil or be empty" if object.blank?
So now you know that the error came from form_for
as a result of passing in nil or an empty array. All that is left is for you to fix it.
I was first introduced to this style of raising early errors after attending a Avdi’s talk about Confident Ruby at RailsConf 2011. If you weren’t fortunate enough to attend one of his talks you can still read his ebook.
Raising early errors with good messages can help immensely, but we can do more. If every time you get a routing error, you have to run $ rake routes
why not put rake routes in your error page. If leaving out arguments causes an error, tell the programmer exactly what they are missing. Even for common sense debugging tips, it can still never hurt to remind someone to check the logs.
These days a heavy emphasis is placed on good documentation as it should be, but docs are only good when you’re looking at them. By writing better errors we are creating living documentation that comes to you exactly when you need it the most. Good error messages are like lessons learned scaled out to thousands of developers.
Next time you find yourself confused when you’re programming, write down any error messages you get. Keep it in evernote or a text file, and when you figured out what went wrong and how to fix it, ask yourself if there was a better way to experience that error. Did Rails have enough information to know what you did wrong? If not, why? If so, why didn’t you get a better error message? Don’t just sit back, relax and let errors pass you bye, go out there - get mad - write code - raise “hell”.
Richard works for Heroku on the Ruby Team and teaches rails classes at the University of Texas. If you like errors as much as he does, chat him up on the twitters @schneems. These error messages and more are available starting with Rails 4.