Latest web development tutorials

Ruby 異常

異常和執行總是被聯繫在一起。 如果您打開一個不存在的文件,且沒有恰當地處理這種情況,那麼您的程序則被認為是低質量的。

如果異常發生,則程序停止。 異常用於處理各種類型的錯誤,這些錯誤可能在程序執行期間發生,所以要採取適當的行動,而不至於讓程序完全停止。

Ruby 提供了一個完美的處理異常的機制。 我們可以在begin/end塊中附上可能拋出異常的代碼,並使用rescue子句告訴Ruby完美要處理的異常類型。

語法

begin #开始
 
 raise.. #抛出异常
 
rescue [ExceptionType = StandardException] #捕获指定类型的异常 缺省值是StandardException
 $! #表示异常信息
 $@ #表示异常出现的代码位置
else #其余异常
 ..
ensure #不管有没有异常,进入该代码块
 
end #结束

beginrescue中的一切是受保護的。 如果代碼塊執行期間發生了異常,控制會傳到rescueend之間的塊。

對於begin塊中的每個rescue子句,Ruby把拋出的異常與每個參數進行輪流比較。 如果rescue 子句中命名的異常與當前拋出的異常類型相同,或者是該異常的父類,則匹配成功。

如果異常不匹配所有指定的錯誤類型,我們可以在所有的rescue子句後使用一個else子句。

實例

#!/usr/bin/ruby

begin
   file = open("/unexistant_file")
   if file
      puts "File opened successfully"
   end
rescue
      file = STDIN
end
print file, "==", STDIN, "\n"

以上實例運行輸出結果為。 您可以看到,STDIN取代了file,因為打開失敗。

#<IO:0xb7d16f84>==#<IO:0xb7d16f84>

使用retry語句

您可以使用rescue塊捕獲異常,然後使用retry語句從開頭開始執行begin塊。

語法

begin
    # 这段代码抛出的异常将被下面的 rescue 子句捕获
rescue
    # 这个块将捕获所有类型的异常
    retry  # 这将把控制移到 begin 的开头
end

實例

#!/usr/bin/ruby

begin
   file = open("/unexistant_file")
   if file
      puts "File opened successfully"
   end
rescue
   fname = "existant_file"
   retry
end

以下是處理流程:

  • 打開時發生異常。
  • 跳到rescue。 fname 被重新賦值。
  • 通過retry 跳到begin 的開頭。
  • 這次文件成功打開。
  • 繼續基本的過程。

注意:如果被重新命名的文件不存在,本勢力代碼會無限嘗試。所以異常處理時,謹慎使用retry

使用raise語句

您可以使用raise語句拋出異常。 下面的方法在調用時拋出異常。 它的第二個消息將被輸出。

語法

raise 

或

raise "Error Message" 

或

raise ExceptionType, "Error Message"

或

raise ExceptionType, "Error Message" condition

第一種形式簡單地重新拋出當前異常(如果沒有當前異常則拋出一個RuntimeError)。 這用在傳入異常之前需要解釋異常的異常處理程序中。

第二種形式創建一個新的RuntimeError異常,設置它的消息為給定的字符串。 該異常之後拋出到調用堆棧。

第三種形式使用第一個參數創建一個異常,然後設置相關的消息為第二個參數。

第四種形式與第三種形式類似,您可以添加任何額外的條件語句(比如unless )來拋出異常。

實例

#!/usr/bin/ruby

begin  
    puts 'I am before the raise.'  
    raise 'An error has occurred.'  
    puts 'I am after the raise.'  
rescue  
    puts 'I am rescued.'  
end  
puts 'I am after the begin block.'  

以上實例運行輸出結果為:

I am before the raise.  
I am rescued.  
I am after the begin block.  

另一個演示raise用法的實例:

#!/usr/bin/ruby

begin  
  raise 'A test exception.'  
rescue Exception => e  
  puts e.message  
  puts e.backtrace.inspect  
end  

以上實例運行輸出結果為:

A test exception.
["main.rb:4"]

使用ensure語句

有時候,無論是否拋出異常,您需要保證一些處理在代碼塊結束時完成。 例如,您可能在進入時打開了一個文件,當您退出塊時,您需要確保關閉文件。

ensure子句做的就是這個。 ensure 放在最後一個rescue 子句後,並包含一個塊終止時總是執行的代碼塊。 它與塊是否正常退出、是否拋出並處理異常、是否因一個未捕獲的異常而終止,這些都沒關係, ensure塊始終都會運行。

語法

begin 
   #.. 过程
   #.. 抛出异常
rescue 
   #.. 处理错误 
ensure 
   #.. 最后确保执行
   #.. 这总是会执行
end

實例

begin
  raise 'A test exception.'
rescue Exception => e
  puts e.message
  puts e.backtrace.inspect
ensure
  puts "Ensuring execution"
end

以上實例運行輸出結果為:

A test exception.
["main.rb:4"]
Ensuring execution

使用else語句

如果提供了else子句,它一般是放置在rescue子句之後,任意ensure之前。

else子句的主體只有在代碼主體沒有拋出異常時執行。

語法

begin 
   #.. 过程 
   #.. 抛出异常
rescue 
   #.. 处理错误
else
   #.. 如果没有异常则执行
ensure 
   #.. 最后确保执行
   #.. 这总是会执行
end

實例

begin
 # 抛出 'A test exception.'
 puts "I'm not raising exception"
rescue Exception => e
  puts e.message
  puts e.backtrace.inspect
else
   puts "Congratulations-- no errors!"
ensure
  puts "Ensuring execution"
end

以上實例運行輸出結果為:

I'm not raising exception
Congratulations-- no errors!
Ensuring execution

使用$! 變量可以捕獲拋出的錯誤消息。

Catch 和Throw

raise 和rescue 的異常機制能在發生錯誤時放棄執行,有時候需要在正常處理時跳出一些深層嵌套的結構。 此時catch 和throw 就派上用場了。

catch定義了一個使用給定的名稱(可以是Symbol或String)作為標籤的塊。 塊會正常執行知道遇到一個throw。

語法

throw :lablename
#.. 这不会被执行
catch :lablename do
#.. 在遇到一个 throw 后匹配将被执行的 catch
end

或

throw :lablename condition
#.. 这不会被执行
catch :lablename do
#.. 在遇到一个 throw 后匹配将被执行的 catch
end

實例

下面的實例中,如果用戶鍵入'!' 回應任何提示,使用一個throw 終止與用戶的交互。

def promptAndGet(prompt)
   print prompt
   res = readline.chomp
   throw :quitRequested if res == "!"
   return res
end

catch :quitRequested do
   name = promptAndGet("Name: ")
   age = promptAndGet("Age: ")
   sex = promptAndGet("Sex: ")
   # ..
   # 处理信息
end
promptAndGet("Name:")

上面的程序需要人工交互,您可以在您的計算機上進行嘗試。 以上實例運行輸出結果為:

Name: Ruby on Rails
Age: 3
Sex: !
Name:Just Ruby

類Exception

Ruby 的標準類和模塊拋出異常。 所有的異常類組成一個層次,包括頂部的Exception 類在內。 下一層是七種不同的類型:

  • Interrupt
  • NoMemoryError
  • SignalException
  • ScriptError
  • StandardError
  • SystemExit

Fatal 是該層中另一種異常,但是Ruby 解釋器只在內部使用它。

ScriptError 和StandardError 都有一些子類,但是在這裡我們不需要了解這些細節。 最重要的事情是創建我們自己的異常類,它們必須是類Exception 或其子代的子類。

讓我們看一個實例:

class FileSaveError < StandardError
   attr_reader :reason
   def initialize(reason)
      @reason = reason
   end
end

現在,看下面的實例,將用到上面的異常:

File.open(path, "w") do |file|
begin
    # 写出数据 ...
rescue
    # 发生错误
    raise FileSaveError.new($!)
end
end

在這裡,最重要的一行是raise FileSaveError.new($!)。 我們調用raise 來示意異常已經發生,把它傳給FileSaveError 的一個新的實例,由於特定的異常引起數據寫入失敗。