Sunday 29 April 2012

Exception Handling in Ruby part 2(Raising an Exception / User defined Exception)

Sometimes the neither the system nor the language detect an error, but you do. Perhaps the user input someone 18 years old for Medicare. Linux doesn't know that's wrong. Ruby doesn't know that's wrong. But you do.

You can raise a generic exception (or the current exception if there is one) like this:
raise if age < 65


begin
 age = 60
 puts 'Here 1'
 raise MyError.new "Text" unless age < 65
 puts 'Here 2'
rescue MyError
 puts 'Here 3 - MyError encountered'
 retry   // here if you want to run that code which is produced error use retry
rescue StandardError
 puts "Here 4 - Other error encountered (#{$!.inspect})" + caller.inspect
 raise
else
 puts 'Here 5 - No errors'
ensure
 puts 'Here 6 - Always done'
end




Exceptions are handled by blocks. The risky code goes into the first chunk of the block. If an exception is encountered, the program will jump to the end of that chunk and look for an appropriate action. In the above example, it will first look at rescue MyError. If the exception is of the MyError class - or a sub-class - then the program will not run the code folling the rescue. Note that you can list multiple exception classes, separated by commas.

Otherwise, it will look further. The next rescue is for StandardError, so this will catch all errors of the StandardError type or its sub-classes - except for MyClass, as that would have been caught earlier. All rescuable exceptions must inherit from StandardError (but see later), so this is set up to catch all exceptions, but that need not be the case; uncaught exceptions will be passed on up the stack to whatever else might handle them.

The else chunk will get performed after the initial chunk has completed successfully (i.e., without raising an exception). Finally, the ensurechunk gets performed whatever the outcome.

An exception is raised using the raise keyword. Exceptions are just objects, so are instantiated just like any other object. They typically take a string parameter, which you can use to give a descriptive message. Alternatively, you use this form:
raise MyError.new "Test"
raise MyError, "Text", caller
raise MyError, "Text"
raise "Text"     # For the default RuntimeError

You can put a retry in a rescue chunk, as in the example above. The program will jump back to the start of the block, and start again. In the second rescue chunk, there is a raise on its own. This will pass the exception ouside of the block, to be handled by some higher up error handling system.

Also in that chunk, note that the $! code is the exception. If you prefer, you can set your own name for this.
rescue StandardError => err
 puts "Here 4 - Other error encountered (#{err.inspect})" + caller.inspect
raise
Here is the output from the first example:
Here 1
Here 3 - MyError encountered
Here 1
Here 3 - MyError encountered
Here 1
Here 3 - MyError encountered
Here 1
Here 3 - MyError encountered
Here 1
Here 3 - MyError encountered
Here 1
Here 2
Here 5 - No errors
Here 6 - Always done

Stack Trace
To see the stack trace, use the backtrace method. This returns an array of strings. To just see the top dozen entries, you could use this:
$!.backtrace[0..12] * "\n"


Exceptions that do not inherit from StandardError
The superclass for all exceptions is Exception. The theory is that all exceptions that a program should be expected to recover from are either StandardError or a subclass of it, which is why I described them as "rescuable exceptions " earlier. If no exception is specified, rescue will default to StandardError.

However, there may be times you want to recover from other exceptions. A particular example I came across is a SyntaxError thrown by ERB. The offending syntax was in a data file, not my application; my application should have caught the error, and reported it back to the user. This is entirely different to a syntax error in my code, and in my opinion ERB should throw its own exceptions that inherit fromStandardError. Time-out errors are another example, as discussed here.

To catch all exceptions use:
rescue Exception


The rescue statement modifier
Rescue can be used in a way analogous to if and unless. For example:
test rescue do_stuff

This statement will run the method test, and if it encounters an error (specifically StandardError or subclasses) it will invoke do_stuff. You can use this to assign a default value if a method fails, like this:
s = test rescue "Default value"

The rescue stament returns the value "Default Value". You could write it with brackets, which might help make sense of it:
s = (test rescue "Default value")

You can concatenate statement with semi-colons, but cannot use blocks.
s = test rescue do_stuff; "Default value"


Catch and Throw
The catch and throw facility in Ruby is not really exception handling at all, though it does have similarities. As it shares keywords with Java exception handling, it seems to get lumped into any discussion on exceptions. This page is no different.

Calling a throw will interrupt the program flow, causing it to jump to the named catch. Use this format to set up a catch block. Any timethrow :my_label is called within the block (including within methods called from the block).
catch :my_label do
 do_stuff
throw :my_label
 do_not_do this_stuff
end

You can use the throw to return a value to catch (which will be nil otherwise).
value = catch :my_label do
 do_stuff
throw :my_label, "My result"
 do_not_do this_stuff
end

No comments:

Post a Comment