1. 程式人生 > >Ruby學習之異常處理機制

Ruby學習之異常處理機制

異常和執行總是被聯絡在一起,假如我們要開啟一個不存在的檔案,但是沒有處理這種情況,那麼,程式執行的後果就可想而知了,很明顯,程式停止執行。而異常就是用於處理各種型別的錯誤,這些錯誤可能在程式執行期間發生,所以要採取適當的行動,而不至於讓程式完全停止。Ruby有一套非常完善的處理異常的機制,就是我們可以在begin或者end塊中,附上可能丟擲異常的程式碼,並且使用rescue子句告訴程式如何處理,語法非常簡單,我們來看下:

begin #開始
 
 raise.. #丟擲異常
 
rescue [ExceptionType = StandardException] #捕獲指定型別的異常 預設值是StandardException
 $! #表示異常資訊
 
[email protected]
#表示異常出現的程式碼位置 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 ,因為開啟失敗。

我們可以使用 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 "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.'

再來看個演示 raise 用法的例項:

#!/usr/bin/ruby
 
begin  
  raise 'A test exception.'  
rescue Exception => e  
  puts e.message  
  puts e.backtrace.inspect  
end

有時候,無論是否丟擲異常,我們需要保證一些處理在程式碼塊結束時完成。例如,我們可能在進入時打開了一個檔案,當我們退出塊時,我們需要確保關閉檔案。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

我們如果提供了 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

咱們再次重申下,使用 $! 變數可以捕獲丟擲的錯誤訊息。

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:")

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

在上述程式碼中,最重要的一行是 raise FileSaveError.new($!)。我們呼叫 raise 來示意異常已經發生,把它傳給 FileSaveError 的一個新的例項,由於特定的異常引起資料寫入失敗。

好啦,本次記錄就到這裡了。

如果感覺不錯的話,請多多點贊支援哦。。。