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
, 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 |
|
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 |
|
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 |
|
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 |
|
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/