A Programming Primer for Counting and Other Unconventional Tasks

Exception and Error Handling

How a program recovers from unexpected (and expected) errors
exception-handling
Planking on the High Line. (Photo by Dan Nguyen)

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:

a = 10
b = "42"

begin
   a + b
rescue
   puts "Could not add variables a (#{a.class}) and b (#{b.class})"
else
   puts "a + b is #{a + b}"
end

Executing the revised code gets us this:

Two obvious differences from the first try: The puts statement in the rescue clause executed. And more importantly, the Ruby program did not crash.

Let's feed this simple operation with an array of values of different types to see how the else clause comes into play:


values = [42, 'a', 'r', 9, 5, 10022, 8.7, "sharon", "Libya", "Mars", "12", 98, rand + rand, {:dog=>'cat'}, 100, nil, 200.0000, Object, 680, 3.14, "Steve", 78, "Argo"].shuffle

while values.length > 0
  a = values.pop
  b = values.pop
  

  begin
     a + b
  rescue
     puts "Could not add variables a (#{a.class}) and b (#{b.class})"
  else
     puts "a + b is #{a + b}"
  end
  
end  
With user input

This next demonstration shows how exception-handling can be used in an application in which you accept user input.

For this section, you will have to go to your command line to run it; it won't work from your text-editor. You can refer back to the installation chapter if you've forgotten how to do this.

The steps:

  1. Open an empty text file and enter a "Hello world" script into it.
  2. Save it in a directory that's easy to get to. Something like ~/Documents/extest.rb
  3. Open up your command prompt
  4. Navigate to the directory of the file: cd ~/Documents
  5. Run the Ruby interpreter (not irb):
    ruby ex-test.rb

    The command line should look something like this:

Note: My command prompt has ~ :) because I configured it to be a smiley face. Yours is probably just a dollar sign $

Save the following script into a file and execute it from the command line:

while 1
   puts "Enter a number>>"
   num = Kernel.gets.match(/\d+/)[0]
   puts "#{num} + 1 is: #{num+1}"
end

You should immediately recognize that this script consists of an infinite loop, as while 1 always 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:

~ :)  ruby extest.rb 
Enter a number
6
extest.rb:4:in `+': can't convert Fixnum into String (TypeError)
from extest.rb:4

So there's a TypeError in line 4; the fix is obvious. Change:

puts "#{num} + 1 is: #{num+1}"

To:

puts "#{num} + 1 is: #{num.to_i+1}"

Now run extest.rb:

~ :)  ruby extest.rb
Enter a number>>
6
6 + 1 is: 7
Enter a number>>

This goes on and on. Unless you break it, of course. Type in a non-number:

~ :)  ruby extest.rb
Enter a number>>
No way
extest.rb:3: undefined method `[]' for nil:NilClass (NoMethodError)

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:

while 1
   puts "Enter a number>>"
   num = Kernel.gets.to_i.to_s.match(/\d+/)[0]
   puts "#{num} + 1 is: #{num.to_i+1}"
end
~ :)  ruby extest.rb
Enter a number>>
Yo
0 + 1 is: 1
Enter a number>>

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.

This is where the begin/rescue block comes in:


while 1
   puts "Enter a number>>"
   begin
     num = Kernel.gets.match(/\d+/)[0]
   rescue
     puts "Erroneous input! Try again..."
   else  
     puts "#{num} + 1 is: #{num.to_i+1}"
   end  
end

The resulting output:

~ :)  ruby extest.rb
Enter a number>>
8
8 + 1 is: 9
Enter a number>>
eight
Erroneous input! Try again...
Enter a number>>
8
8 + 1 is: 9
Enter a number>>
Press Ctrl-C to break out of program execution.

If a failure occurs, the program enters the rescue branch of code; else, the program goes on as normal. We now have a program that both:

  • Notifies the user of the existence of an error
  • Does not simply crash out because of the error
Like an if/else

There doesn't seem to be much difference between begin/rescue/else and a simpler if/else construct, does there?

while 1
     puts "Enter a number>>"
     if num = Kernel.gets.match(/\d+/) 
      num = num[0]
      puts "#{num} + 1 is: #{num.to_i+1}"
   else   
    puts "Erroneous input! Try again..."
  end  
end

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.


require 'open-uri'
require 'timeout'

remote_base_url = "http://en.wikipedia.org/wiki"

start_year = 1900
end_year = 2000

(start_year..end_year).each do |yr|
 begin
   rpage = open("#{remote_base_url}/#{yr}")
 rescue StandardError=>e
   puts "Error: #{e}"
 else
   rdata = rpage.read
 ensure   
   puts "sleeping"
   sleep 5
 end
     
 if rdata
   File.open("copy-of-#{yr}.html", "w"){|f| f.write(rdata) }
 end
end   
begin
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:

for i in 'A'..'C'
  retries = 2
  begin
    puts "Executing command #{i}"
    raise "Exception: #{i}"
  rescue Exception=>e
    puts "\tCaught: #{e}"
    if retries > 0
      puts "\tTrying #{retries} more times\n"
      retries -= 1
      sleep 2
      retry
    end  
  end
end

The output:

Executing command A
   Caught: Exception: A
   Trying 2 more times
Executing command A
   Caught: Exception: A
   Trying 1 more times
Executing command A
   Caught: Exception: A
Executing command B
   Caught: Exception: B
   Trying 2 more times
Executing command B
   Caught: Exception: B
   Trying 1 more times
Executing command B
   Caught: Exception: B
Executing command C
   Caught: Exception: C
   Trying 2 more times
Executing command C
   Caught: Exception: C
   Trying 1 more times
Executing command C
   Caught: Exception: C
Using retry with OpenURI

The following snippet of code attempts to download pages from Wikipedia. The third entry, xj3490, refers to a non-existent page and is guaranteed to fail:

require 'open-uri'
remote_base_url = "http://en.wikipedia.org/wiki"

[1900, 1910, 'xj3490', 2000].each do |yr|
 
 retries = 3
 
 begin
   url = "#{remote_base_url}/#{yr}"
   puts "Getting page #{url}"
   rpage = open(url)
 rescue StandardError=>e
   puts "\tError: #{e}"
   if retries > 0
       puts "\tTrying #{retries} more times"
       retries -= 1
       sleep 1
       retry
   else
       puts "\t\tCan't get #{yr}, so moving on"
   end    
 else
   puts "\tGot page for #{yr}"
 ensure   
   puts "Ensure branch; sleeping"
   sleep 1

 end
end

The output is:

Getting page http://en.wikipedia.org/wiki/1900
   Got page for 1900
Ensure branch; sleeping
Getting page http://en.wikipedia.org/wiki/1910
   Got page for 1910
Ensure branch; sleeping
Getting page http://en.wikipedia.org/wiki/xj3490
   Error: 403 Forbidden
   Trying 3 more times
Getting page http://en.wikipedia.org/wiki/xj3490
   Error: 403 Forbidden
   Trying 2 more times
Getting page http://en.wikipedia.org/wiki/xj3490
   Error: 403 Forbidden
   Trying 1 more times
Getting page http://en.wikipedia.org/wiki/xj3490
   Error: 403 Forbidden
      Can't get xj3490, so moving on
Ensure branch; sleeping
Getting page http://en.wikipedia.org/wiki/2000
   Got page for 2000
Ensure branch; sleeping

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.

You can see the family tree of Exception here.

Example: Casting a wide rescue-net for exceptions

Let's return to the chapter's opening example, but slightly altered to print out the type of error. Remember that you must run it from the command line:


while 1
   puts "Enter a number>>"
   begin
     num = Kernel.gets.match(/\d+/)[0]
   rescue StandardError=>e
     puts "Erroneous input!"
     puts e
     puts "\tTry again...\n"
   else  
     puts "#{num} + 1 is: #{num.to_i+1}"
   end  
end

The output:

~ :)  ruby extest.rb
Enter a number>>
5
5 + 1 is: 6
Enter a number>>
a
Erroneous input! 
undefined method `[]' for nil:NilClass
   Try again...
   
Enter a number>>   

Run the script but use Ctrl-C to break out of it. You should see something like this as you are kicked out to the command prompt

Enter a number>>
^Cextest.rb:4:in `gets': Interrupt
   from extest.rb:4
~ :)

Instead of rescuing StandardError – which is the default class of error that is rescued if you don't specify otherwise) – modify the code so that it will rescue the Exception class:

while 1
   puts "Enter a number>>"
   begin
     num = Kernel.gets.match(/\d+/)[0]
   rescue Exception=>e
     puts "Erroneous input!"
     puts e
     puts "\tTry again...\n"
   else  
     puts "#{num} + 1 is: #{num.to_i+1}"
   end  
end

Run it from the command line. It'll execute in the same way as it did before. However, when you try Ctrl-C to break out of the program, you'll find that it won't let you:

~ :) ruby extest.rb
Enter a number>>
7
7 + 1 is: 8
Enter a number>>
a
Erroneous input!
undefined method `[]' for nil:NilClass
   Try again...
Enter a number>>
^CErroneous input!

   Try again...
Enter a number>>
^CErroneous input!

   Try again...
Enter a number>>

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.

In the next section, we'll examine the Exception family tree.

The Exception family tree

Here's a handy family tree of Exception and all of its children errors. This was generated using this handy code snippet from Nick Sieger.

Exception
    NoMemoryError
    ScriptError
        LoadError
        NotImplementedError
        SyntaxError
    SignalException
        Interrupt
    StandardError
        ArgumentError
        IOError
            EOFError
        IndexError
            StopIteration
        LocalJumpError
        NameError
            NoMethodError
        RangeError
            FloatDomainError
        RegexpError
        RuntimeError
        SecurityError
        SystemCallError
        SystemStackError
        ThreadError
        TypeError
        ZeroDivisionError
    SystemExit
    fatal   

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

To be continued...

Further reading

Exception handling - Wikipedia