[Day25] 就算是海軍本部又怎麼樣,艾斯,老爹來救你了!
這篇要介紹的是 Ruby 裡的例外處理(Rescue Exception)
每天都在 coding 的我遇到噴錯已經是家常便飯,而這些錯誤可能是環境、是語法、是邏輯矛盾等等各式各樣的問題所造成。
然而,工程師除了要解決碰到的每個錯誤,有時候甚至可以「未卜先知」*,如果在撰寫程式碼時,就已經先猜到可能會有錯,並告知程式在特定錯誤發生時該如何處理,就能防範於未然,省下噴錯後還得去找問題的時間。
*聽說厲害的工程師還會「通靈」!
什麼是例外處理?
簡單來說,當程式執行時出現了某個預料外的結果,基本上會有兩種情況:
- 如果沒有提供
rescue
的話,執行中止
- 執行
rescue
裡的程式碼
預料外的結果?
「人生不如意事十之八九,程式也會有。」
我們寫程式時總是可能會寫錯,所以噴錯是非常正常的。rubylearning.com 列出了各種可能會產生例外的情況,像是我們很熟悉的SyntaxError
、ArgumentError
、RuntimeError
等等…
附上完整版供參:
這時,就是例外處理(Rescue Exception)登場救火的時機了!
先來看看怎麼寫:
1 2 3 4 5 6 7
| begin rescue ensure end
|
(Ruby 在這裡充分發揮了它的強項:程式碼非常好讀啊!)
整體運作有點像條件判斷,如果沒有出現例外,就不會進到 rescue
。
- 從
begin
到 rescue
是正常執行時的情況
- 一旦無法執行,就會將控制權轉移到
rescue
- 執行
rescue
以下到 end
的區域
begin
來自於 Object
類別,基本上和 end 一起使用,主要是用來處理例外產生的機制,常被統稱為 begin block
或是 begin-end block
。
噴錯也是一種例外處理的方式
平常 Ruby 噴錯所顯示的訊息,實際上也是執行錯誤後產生的一種例外處理。直接看 code 最清楚:
1 2 3 4 5 6 7 8 9 10 11 12
| begin rescue RuntimeError puts "RuntimeErrorr" rescue SyntaxError puts "SyntaxError" rescue ArgumentError puts "ArgumentError" end
|
(這個很重要!沒有的話我就不知道噴錯的原因了!)
用 raise
自己做出錯誤訊息
接著再來看個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| def raise_exception puts "準備執行 raise" raise "久等啦我就是錯誤訊息!" puts "結束執行 raise" end => :raise_exception
raise_exception
準備執行 raise Traceback (most recent call last): 5: from /Users/jrnalts/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `<main>' 4: from /Users/jrnalts/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `load' 3: from /Users/jrnalts/.rvm/rubies/ruby-2.6.5/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>' 2: from (irb):17 1: from (irb):14:in `raise_exception' RuntimeError (久等啦我就是錯誤訊息!)
|
從上面例子裡可以看到, "結束執行 raise"
這行並沒有被印出來,原因是我用 raise
方法觸發了例外處理,使得程式在執行時將控制權交給了 rescue
raise
是什麼?
一個來自於 Kernel
模組的方法,用途是做出一個例外實體,預設是 RuntimeError
要做出其他錯誤類型也行!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def raise_exception puts "準備執行 raise" raise SyntaxError, "你好像寫錯什麼了雖然我不確定" puts "結束執行 raise" end
raise_exception 準備執行 raise Traceback (most recent call last): 5: from /Users/jrnalts/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `<main>' 4: from /Users/jrnalts/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `load' 3: from /Users/jrnalts/.rvm/rubies/ruby-2.6.5/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>' 2: from (irb):29 1: from (irb):26:in `raise_exception' SyntaxError (你好像寫錯什麼了雖然我不確定)
|
把所有的 rescue
都集中起來吧!
如果同時有很多地方需要 rescue
,我們可以透過 rescue from
把所有例外集中處理,這樣就不用每個地方都寫 rescue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class ApplicationController < ActionController::Base rescue_from User::NotAuthorized, :with => :deny_access rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
rescue_from 'MyAppError::Base' do |exception| render :xml => exception, :status => 500 end
protected def deny_access ... end
def show_errors(exception) exception.record.new_record? ? ... end end
|
感覺方便很多呢!
例外處理的 begin
和 end
都可以省略
如果有個例外處理方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| def raise_exception begin puts '咦...是不是?' raise '有大事發生啦!' puts '哇哇哇!' rescue puts '沒事~哥已經處理好了' end end
raise_exception
咦...是不是? 沒事~哥已經處理好了 => nil
|
在這個凡事都有哥處理好的例子裡,如果我們省略了 begin
和 end
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def raise_exception puts '咦...是不是?' raise '有大事發生啦!' puts '哇哇哇!' rescue puts '沒事~哥已經處理好了' end
raise_exception
咦...是不是? 沒事~哥已經處理好了 => nil
|
程式也是不會壞呢!真的很罩!
今天就先介紹到這邊啦!期許自己平常在 coding 時也能記得要用上 rescue
的寫法,減少程式噴錯抓錯的情況不停發生!