程式扎記: [ 文章收集 ] Ruby Exceptions And Exception Handling

標籤

2014年11月5日 星期三

[ 文章收集 ] Ruby Exceptions And Exception Handling

Source From Here 
Preface 
Ruby exceptions and error handling is not the most remarkable feature of the Ruby language. Infact, the way Ruby deals with exceptions is strikingly similar to other languages (such as Java etc.). But, I think all the stuff I’ve been writing about Ruby lately has taken on a life of it’s own for me :), so I am going to quickly go over this topic if only for completeness sakes. 

Raising Exceptions 
Getting Ruby to raise an exception is easy :). Ruby, like many other languages has a hierarchy of exception classes (that all inherit from the class Exception), some of these are familiar and fairly easy to produce, for example ZeroDivisionError or NoMethodError. Here is some code which will do just that: 
 

Of course you don’t have to wait for Ruby to raise exceptions for you, you can do so explicitly in your code with the raise keyword (it is actually a method). Lets write a method where we explicitly raise an exception if the argument we pass in is false: 
  1. def i_must_have_truth(value)  
  2.   raise TypeError, 'You must give me truth' if value == false  
  3. end  
  4.   
  5. i_must_have_truth false  
this prints: 
 

As you can see we are able to raise a specific exception and pass it a message which then gets printed out to the console. There is also a handy shortcut, if you use raise without giving it a specific exceptions (or even without giving it an error message to display), Ruby will automatically raise a RuntimeError for you which is pretty handy: 
  1. def i_must_have_truth(value)  
  2.   raise "Hello"  
  3. end  
  4.   
  5. i_must_have_truth false  
this prints: 
 

Rescuing Exceptions 
So far so good, but life would be pretty tough if we couldn’t handle the exceptions that get thrown in any way, This is where the rescue clause comes in. If we wrap some code in a begin .. end block and put a rescue clause in the middle of that, control will go to the rescue clause if any exception is thrown by the code. Let us demonstrate: 
  1. begin  
  2.   1/0  
  3.   p 'I should never get executed'  
  4. rescue  
  5.   p 'I am rescuing an exception and can do what I want!'  
  6. end  
This produces the following output: 
"I am rescuing an exception and can do what I want!"

As you can see the first string does not get printed since the division by zero will throw an exception and control will pass to the rescue clause, which will print out the second string for us. Using rescue by itself will allow you to rescue all exceptions that get thrown by the code, but sometimes you might want to only capture specific ones and let other code handle all the others. The rescue clause allows you to do this as well: 
  1. i=0  
  2. while i<=10  
  3.   begin  
  4.     if i ==0  
  5.       1/0  
  6.     end  
  7.     raise "random runtime exception"  
  8.     p 'I should never get executed'  
  9.   rescue ZeroDivisionError  
  10.     p 'I am rescuing only ZeroDivisionErrors!'  
  11.     i+=1  
  12.   end  
  13. end  
which gives us: 
 

As you can tell, we rescued the first exceptions since it was a division by zero, but the second exception does not get rescued and so the program exits with an error. However, we sometimes want to execute some code regardless of whether an exception was thrown or not (i.e. we may want to do some cleanup). Java has the finallykeyword for this, Ruby has ensure. We can put an ensure clause within out begin .. end block. The code inside this clause gets executed regardless of whether the code throws an exception or not. For example, if we are reading from a file, we want to close the file handle no matter if an exception is thrown or not, we can do the following: 
  1. file = nil  
  2. begin  
  3.   file = File.open("blah.txt")  
  4.   raise  
  5. rescue  
  6.   p 'I rescue all exception and raise new ones'  
  7. ensure  
  8.   file.close if file!=nil  
  9.   p 'just closed the file'  
  10. end  
as we expect the output is: 
"I rescue all exception and raise new ones"
"just closed the file"
Even though an exception was thrown and rescued, the code in the ensure clause executes regardless. 

Rescuing Exceptions Inside Methods 
If we are inside a method and we want to rescue some exceptions, we don’t actually need a begin .. end block since the method definition itself will act in that capacity. So, we can do something like this: 
  1. def some_method  
  2.   p 'Hello method'  
  3.   raise  
  4.   p 'Bye method'  
  5. rescue  
  6.   p 'Rescuing exceptions'  
  7. end  
  8. some_method  
which print out: 
"Hello method"
"Rescuing exceptions"

We have rescued an exceptions without having a begin .. end block. 

You are not limited to knowing just the type of the exception you’re rescuing, you can get more information at your disposal. When you rescue an exception you can get a hold of the actual exception object that was thrown and then query this object for various details. Here is how: 
  1. begin  
  2.   raise ZeroDivisionError, "Hello I am a random zero division error"  
  3. rescue ZeroDivisionError => e  
  4.   p e.message  
  5.   p e.backtrace  
  6. end  
Execution result: 
"Hello I am a random zero division error"
["C:\\John\\EclipseNTU\\RubyAlg\\Test.rb:2:in `(root)'"]

If we execute the above code we will print out the message as well as the stack trace of the exception we rescued which are provided by the exception object itself. You can also do the same thing with a general rescue clause: 
  1. begin  
  2.   raise "Hello I am a random runtime error"  
  3. rescue => e  
  4.   p e.message  
  5.   p e.backtrace  
  6. end  
Finally if you have rescued an exception, but don’t want to handle it yourself (for whatever strange reason), calling raise within a rescue block with no parameters will allow you to re-raise the original exception that you have rescued rather than the normal behavior (i.e. raising a general runtime exception). 

Creating Your Own Exceptions 
Creating your own exceptions in Ruby is extremely simple, all you need to do is create a class that inherits from Exception or one of it’s descendants and you’re good to go: 
  1. class MyCrazyException < Exception  
  2. end  
  3.   
  4. raise MyCrazyException, "I am a crazy new exception"  
this prints out: 
 

As you can see Ruby is saying that it has raised our new exception rather than one of it’s regular ones. As usual, this allows you to define different types of exceptions for various error conditions in your code which also allows you to rescue them by name. You may or may not want to do this, you can easily get away with just using runtime exceptions but it is up to you. 
Update. 
Several people have pointed out, in the comments that inheriting from Exception directly is a big no-no when creating custom exceptions. You should instead inherit fromStandardErrorThis is because exceptions that inherit from StandardError deal with application level errors, while if you inherit Exception directly you risk catching errors to do with the environment. Also the convention is to end your exceptions with the word Error rather than exceptions e.g.:
  1. class MyCrazyError < StandardError  
  2. end  

The Ruby Exception Hierarchy 
If you’re curious about the kinds of exceptions that Ruby has predefined for you, here is a list: 
 

The only thing that is missing from that list is the Errno family of exceptions. These are a whole set of exceptions that relate to file I/O and live in the Errno namespace. You can see one of these in action if you try to open a file that doesn’t exist, e.g.: 
which gives you an Errno style exception: 
 

You’re now a Ruby exception guru, go forth and use your new Ruby error handling powers for good instead of evil :). 

Supplement 
Ruby 手冊 - 救援 Exception processing: rescue 
Ruby 手冊 - 確認 Exception processing: ensure

沒有留言:

張貼留言

網誌存檔