This was a chapter that I decided to include at the last minute. It's not complete but at least you can be aware of basic exception handling.

No matter how carefully you code your script, your program is prone to failure for reasons beyond your control. A website that your script scrapes may suddenly be down. Or someone sharing the same hard drive may delete a file your program is supposed to read from.

Circumstances such as these will crash your program. For any kind of long continuous task that you don't want to baby-sit and manually restart, you will need to write some exception-handling code to tell the program how to carry on when things go wrong.

Demonstrating exceptions

Before the formal description of the the begin/rescue block, let's walk through a couple examples of it in action. At a skin-deep level, it behaves nearly the same as the if/else construct.

Skipping past an error

The Exception class handles nearly every kind of hiccup that might occur during runtime, including syntax screwups and incorrect type handling.

We learned early on that adding numbers and strings with no type conversion would crash a program:

a = 10
b = "42"
a + b

The attempted arithmetic results in this error:

The begin/rescue block is typically used on code in which you anticipate errors. There's only one line here for us to worry about:

You should immediately recognize that this script consists of an infinite loop, as while 1always evaluates to true (remember to press Ctrl-C if you find yourself in a program that won't quit). However, the interpreter doesn't go in a frenzy because it only moves forward after the user enters input, thanks to the Kernel.gets method.

Yet no matter what you type in, you should get an error. Typing in the number 6, for example, will net you this:

If a user does not enter a number, the match method in line 3 will return nil, which causes the program to crash out. We can protect against user disobedience by sanitizing the input, of course. The following alteration will convert any non-numerical input to 0:

The program is happy now. But why should we have to compromise just because the user ignores simple instructions? Sometimes it's OK to go along and compensate for user error. Other times, it's critical to acknowledge the error and yet carry on.

At this point, no. This example was only meant to show how exception-handling happens in practice. The rest of this chapter will show how exception-handling will allow you to have finer-grain response to unpredictable runtime problems.

The Begin...Rescue block

This is the most basic error handling technique. It starts off with the keyword begin and acts in similar fashion to an if statement in that it your program flows to an alternate branch if an error is encountered.

The main idea is to wrap any part of the program that could fail in this block. Commands that work with outside input, such as downloading a webpage or making calculation something based from user input, are points of failure. Something like puts "hello world" or 1 + 1 is not.

This starts off the exception-handling block. Put in the operation(s) that is at risk of failing in this clause. In the above example, the open method for retrieving the webpage will throw an exception if the website is down. (ruby-doc definition)

rescue StandardError=>e

This is the branch that executes if an exception or error is raised. Possible exceptions include: the website is down, or that it times out during a request. The rescue clause includes the code we want to execute in the event of an error or exception (there's a difference between the Ruby Exception and Error classes, which I will get to in a later revision).

In this particular rescue clause, I specify that we want this branch of code to execute when a StandardError (Ruby errors have their own classes) occurs. The actual error object will be stored in the variable named e

In this example, the rescue clause only executes a puts statement, printing out the contents of e

else

If all goes well, this is where the program branches to. In this example, we save the contents of the open method to a variable.
(ruby-doc definition)

ensure

This branch will execute whether an error/exception was rescued or not. Here, we've decided to sleep for 3 seconds no matter the outcome of the open method.
(ruby-doc definition)

Note: The word retry may be unfamiliar to you. Nonetheless, you can guess what it does here. I cover it formally later in this chapter.

Flow of exception handling

Exception handling is a powerful mechanism in programming. And like all powerful features, the correct and incorrect use of it will have large effects on how reliable and maintainable your script is.

Among its hardest to grasp effects is its ability to break flow in a program, even more so than your standard if/else statement.

Using retry

The retry statement redirects the program back to the begin statement. This is helpful if your begin/rescue block is inside a loop and you want to retry the same command and parameters that previously resulted in failure.

Here's a simple example; I use the raise statement to create my own Exception to be caught:

As you can see in the highlighted code above, the ensure branch is skipped by the retry. The retry statement can be very useful but because of the "jump" it creates in your program flow, take care in using it so that your script isn't difficult to understand. And of course, if you don't have some kind of limiting condition, such as retries > 0 – just a simple decrementing variable I set up for this script – your script will end up in an infinite loop.

Exception and Error Classes

Not all errors are the same. And so when designing your exception handling blocks, you may find it necessary to write rescue statements for specific errors, rather than just a catch-all rescue statement as we've done so far.

This section will make more sense if you have a little understanding of object-oriented programming. If you don't have time to read the chapter on it, the basic concept as it relates to exceptions and errors is this:

Every type of error and exception is derived from the Exception class

If your code rescues a StandardError, it will only rescue errors that are derived from StandardError.

If your code rescues an Exception, it will basically handle every possible error that could happen, including all errors of StandardError type and its children types.

Highlighted in red is where I've attempted to break out of the program. Why not?

Unfortunately it won't print out the type of exception, but what's happening is that Ctrl-C creates an Interrupt-type exception. But because our program is designed to rescue Exception, which includes Interrupt, the program "rescues" our Ctrl-C action. Thus, we can't use that to break out of the program (you'll just have to shut down your command line window to get out of it.)

The main lesson here is that while it may be convenient to rescue everything, it may cause unwanted effects and behavior. Be specific when possible.

As you can see, the StandardError class covers just about any kind of syntax-type error. For example, if your code tries to read from a file that doesn't exist:

Oops: No such file or directory - somefilethatdoesntactuallyexist.txt
(Errno::ENOENT)

The output:

Because the attempt to read a non-existing file causes an error in the operating system, Ruby has a special object called Errno to interpret the operating system-specific code. In this case, that operating system-specific code is ENOENT, and the error message is "No such file or directory". This all falls under SystemCallError