Jacob's Programming Explosions

Error Handling in Ruby

Errors. You’ve seen ‘em. Hundreds…Thousands…and that’s just today! You might think errors are bad. But actually, errors are really good. It’s bugs that are bad. Errors are good because they give you clues about why your program doesn’t work, instead of leaving you completely on your own to trace down what is in many cases only a measely typo. Errors may be frustrating, but they are very good at telling us where and how our program crashed. Reading error messages intelligently is important, but you aren’t constrained to only read errors after they terminate your program, you can actually tell your program how to respond to different types of errors! This is called error handling.

If you are anything like me, you’ve probably run into some code in Ruby that you know has to do with errors, but you have no idea what they mean or why they are there. I’m talking about stuff that looks like:

1
2
3
4
5
6
7
8
9
begin
  raise . . .
rescue . . .
  . . .
else
  . . .
ensure
  . . .
end

begin, raise, rescue, ensure, what are all these about? I was wondering that too. So I did some reading and played around a bit. It’s all really simple, but for a long time it was easy for me to ignore these blocks of code and be on my way. But I was missing out. So let’s break that begin/end block apart.

First though, let’s lay some ground work here for our understanding of errors and exceptions in general. What should we know about errors and exceptions? Well, errors are classes that are all descendants of the Exception class. The difference between an exception and an error in Ruby is (in my understanding) very subtle, so we won’t go into that. You can safely think of them as synonymous at a general level. I’ll only be using the term error from this point on.

We should also note that when an error occurs in a program, this is known as an error being raised. Ruby automatically raises errors whenever your program meets the conditions of any of Ruby’s Exception class or it’s descendants. So if you try "x".pop, Ruby will raise the NoMethodError. You can manually raise an error anywhere in a program by using raise, like so: raise RuntimeError, "This is a custom message". Were you to leave that line of code out in the open, it would crash your program every time. If the program does not provide any code for dealing with such an error, the program will terminate. You can however provide your program with instructions concerning what to do when a certain type of error is raised as to prevent the program from terminating. This is known as error handling, which brings us the the begin/end block.

A begin/end block is used to handle errors from within a program. Say we have a program that takes user input and then applies some logic to it in order to yield the program’s final output. This is all well and good until the user pulls a fast one and supplies shoddie input, which is sure to break once we try applying the logic to it. The program crashes. Wouldn’t it be better if we anticipated this kind of tomfoolery in such a way as to have our control flow handle the error that bad input would raise and prompt the user for input again if such was the case? That’s what error handling, and more specifically the begin/rescue block is for. Let’s look at a begin/rescue block in its simplest form.

1
2
3
4
5
begin
  raise StandardError, "This is my optional custom error message"
rescue
  puts "I just delt with that error."
end

Here we raise a StandardError and supply it with an option custom error message. As we learned before, this would normally terminate the program, as errors do. But within a begin/rescue block with a rescue clause, we are given the opportunity to fix whatever caused the error and continue with the program. If you want to make a fix and start from the top of the block just to make sure it worked you can use retry at the bottom of the rescue clause. Maybe that example seems weird to you because I raised an error for seemingly no reason, so let’s do it again where an error is raised based on a conditional statement.

1
2
3
4
5
6
7
8
9
10
11
begin
  days = %{sunday monday tuesday wednesday thursday friday}
  puts "Please enter a day of the week"
  input = gets.strip.downcase
  raise StandardError, "You did not follow the instructions." unless days.include?(input)
rescue
  puts "Okay, I'll pick a day for you. Monday."
  input = "monday"
end

# program uses input for stuff down here.

In this example we ask the user for input and we expect it to be a day of the week. The StandardError is only raised if the conditional is not met, in which case we jump to the rescue clause. Here we tell the user what it is and choose monday on their behalf. This way, we are able to continue the program with a valid value for input that will work later on in the code. We could just put retry in the rescue clause in order to give the user another shot(s).

There are two more components of the begin/rescue block worth touching on quickly. First is the else clause. This is simple what will run if no error is raised.

1
2
3
4
5
6
7
begin
  # Code that may or may not raise an error
rescue
  # Error handling code
else
  # This runs only if no error gets raised.
end

Simple enough. The last piece is ensure. This one is easy too. Whatever is in the ensure clause will run at the very end of the begin/rescue block. It doesn’t matter if an error was raised or not, or if there was an else clause or not. It just always happens, and it happens last. Like so:

1
2
3
4
5
6
7
8
9
10
11
a, b = 1, "x"

begin
  a + b
rescue TypeError
  puts "You can't add those types of objects together"
else
  puts "a + b worked fine, no rescue needed"
ensure
  puts "I print every time no matter what"
end

All in all it’s pretty simple. But why would you want to write error handling code in your programs? Well, you certainly don’t always want to. It should only be used for exceptional cases. And granted, the examples I used were too simple to actually warrant error handling. My intention was to focus more on the basic syntax and mechanics of error handling than the scenarios in which it is appropriate. Hopefully this article help you understand the very basics of handling errors in your Ruby programs.

Here are some resources I found particularly helpful:

General reading on exceptions: http://www.tutorialspoint.com/ruby/ruby_exceptions.htm http://ruby.bastardsbook.com/chapters/exception-handling/ http://rubymonk.com/learning/books/4-ruby-primer-ascent/chapters/41-exceptions/lessons/92-handling#solution4253

When/how to use rescue for handling exceptions: http://daniel.fone.net.nz/blog/2013/05/28/why-you-should-never-rescue-exception-in-ruby/ http://rubymonk.com/learning/books/4-ruby-primer-ascent/chapters/41-exceptions/lessons/93-throw-and-catch

Throw and Catch: http://rubylearning.com/blog/2011/07/12/throw-catch-raise-rescue-im-so-confused/