So you’ve written a program — something a little more complicated than “Hello World!” — and it’s broken. And worst of all, you don’t know why.
An approximation of you when your taco-program falls apart via GIPHY
Sure, part of Ruby’s human-friendly nature is detailed error messages, pointing you exactly to where things went wrong. But sometimes Ruby can only show you the most obvious instance of your program breaking, when actually the problems are a bit more complicated — like when you see an error for calling a method on nil because some data didn’t pass through correctly.
If your program includes information from a webpage or API, this can be particularly frustrating (and likely to happen). Suddenly, none of the data you expect is passing through, and whole classes and modules are passing an empty object through until the program breaks. Calling binding.pry works well in development, but what if you want to show off your cool new program (that you know works) and the unthinkable happens — your terminal fills with errors! What’s a budding programmer to do?
Maybe the program just doesn’t like you? via GIPHY
Enter exception handling. In this case, let’s assume we’re writing code to handle standard errors in Ruby — but in the next post, I’ll write about defining your own exceptions. Rescue clauses are not entirely unlike conditionals: both establish certain a circumstance under which to execute some code. In fact, we could perhaps patch together a sort of error handling using if/else:
What’s the right syntax for exception handling?
Rescue offers another option for the program to run in case of failure. It doesn’t need to be paired with anything else to work — simply writing “rescue” and a block of code after it will give our program something to do in case of emergency.
It’s completely fine to use rescue without the begin/end block. In fact, we can write a one-liner like this:
In this example, we’re using ActiveRecord’s .find_by_id method; if the ID doesn’t exist, we’ll create a new instance of Person. Doot doot!
Rescue blocks can include additional clauses to extend their functionality:
- Else: Although rescue isn’t often used with an else clause, specifying code to run if there’s no error can make your code a bit more explicit or specific.
- Ensure: As shown in the first example, ensure demarcates code that should run whether there’s an error or not.
- Values: We can create a key-value pair with rescue as the key and the error as the value. This has two purposes. First, we can specify particular errors to rescue. (Without specifying, it is assumed that the rescue clause only applies to a StandardError.) Second, creating this pair also allows us to access the value, which is particularly useful if we want to print it somewhere:
- Retry: Maybe we need a particular input from the user, or maybe we want to access a certain resource that might take some time. Adding retry into the mix will — you guessed it — retry the preceding code until no error is raised.
When would exception handling improve my code and/or life?
In my opinion, rescue is particularly useful in any program that requires outside resources. For example, if you need to scrape a website, what will you do if the website’s server is down? If you’re making API calls, what happens when you’ve maxed out your calls? Beyond those use cases, rescue can help simplify debugging in a more complex program. If we tell the rescue block to print the error somewhere, we can create a useful error log to examine when things don’t go as expected.
Rescue in action
One project in my web development course at the Flatiron School involved using an API in a CLI application. My partner and I used Flickr to search for an image tag, then set the first image as the desktop wallpaper. It worked for a day — but then the API key expired. A perfect opportunity to try some error handling.
A note: this error handling could not be more basic, and doesn’t particularly add any functionality. But it looks a little nicer!