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 的一個新的實例,由於特定的異常引起資料寫入失敗。