例外處理¶
Crystal 處理錯誤的方式是透過拋出和捕獲例外。
拋出例外¶
您可以使用呼叫頂層的 raise
方法來拋出例外。與其他關鍵字不同,raise
是一個具有兩個多載的常規方法:一個接受 String,另一個接受 Exception 實例
raise "OH NO!"
raise Exception.new("Some error")
String 版本只會建立一個帶有該訊息的新的 Exception 實例。
只有 Exception
實例或子類別才能被拋出。
定義自訂例外¶
要定義自訂的例外型別,只需從 Exception 繼承即可
class MyException < Exception
end
class MyOtherException < Exception
end
您可以像往常一樣,為您的例外定義建構子,或者只使用預設的建構子。
捕獲例外¶
要捕獲任何例外,請使用 begin ... rescue ... end
表達式
begin
raise "OH NO!"
rescue
puts "Rescued!"
end
# Output: Rescued!
要存取捕獲的例外,您可以在 rescue
子句中指定一個變數
begin
raise "OH NO!"
rescue ex
puts ex.message
end
# Output: OH NO!
要僅捕獲一種例外型別(或其任何子類別)
begin
raise MyException.new("OH NO!")
rescue MyException
puts "Rescued MyException"
end
# Output: Rescued MyException
有效的型別限制是 ::Exception
的子類別、模組型別和這些的聯合。
要存取它,請使用類似於型別限制的語法
begin
raise MyException.new("OH NO!")
rescue ex : MyException
puts "Rescued MyException: #{ex.message}"
end
# Output: Rescued MyException: OH NO!
可以指定多個 rescue
子句
begin
# ...
rescue ex1 : MyException
# only MyException...
rescue ex2 : MyOtherException
# only MyOtherException...
rescue
# any other kind of exception
end
您也可以透過指定聯合型別來同時捕獲多種例外型別
begin
# ...
rescue ex : MyException | MyOtherException
# only MyException or MyOtherException
rescue
# any other kind of exception
end
else¶
只有在沒有捕獲任何例外的情況下,才會執行 else
子句
begin
something_dangerous
rescue
# execute this if an exception is raised
else
# execute this if an exception isn't raised
end
只有在至少指定一個 rescue
子句時,才能指定 else
子句。
ensure¶
無論是否拋出例外,都會在 begin ... end
或 begin ... rescue ... end
表達式結束時執行 ensure
子句
begin
something_dangerous
ensure
puts "Cleanup..."
end
# Will print "Cleanup..." after invoking something_dangerous,
# regardless of whether it raised or not
或者
begin
something_dangerous
rescue
# ...
else
# ...
ensure
# this will always be executed
end
ensure
子句通常用於清理、釋放資源等。
簡短語法形式¶
例外處理有一種簡短的語法形式:假設方法或區塊定義是一個隱含的 begin ... end
表達式,然後指定 rescue
、else
和 ensure
子句
def some_method
something_dangerous
rescue
# execute if an exception is raised
end
# The above is the same as:
def some_method
begin
something_dangerous
rescue
# execute if an exception is raised
end
end
帶有 ensure
def some_method
something_dangerous
ensure
# always execute this
end
# The above is the same as:
def some_method
begin
something_dangerous
ensure
# always execute this
end
end
# Similarly, the shorthand also works with blocks:
(1..10).each do |n|
# potentially dangerous operation
rescue
# ..
else
# ..
ensure
# ..
end
型別推斷¶
在例外處理器的 begin
部分內宣告的變數,在 rescue
或 ensure
主體內考慮時也會取得 Nil
型別。例如
begin
a = something_dangerous_that_returns_Int32
ensure
puts a + 1 # error, undefined method '+' for Nil
end
即使 something_dangerous_that_returns_Int32
從未拋出,或者 a
被賦值,然後執行可能會拋出的方法,也會發生上述情況
begin
a = 1
something_dangerous
ensure
puts a + 1 # error, undefined method '+' for Nil
end
儘管顯然 a
將始終被賦值,但編譯器仍會認為 a
可能從未有機會被初始化。即使這種邏輯未來可能會有所改進,但現在它會迫使您將例外處理器保持在必要的最小值,使程式碼的意圖更加清晰
# Clearer than the above: `a` doesn't need
# to be in the exception handling code.
a = 1
begin
something_dangerous
ensure
puts a + 1 # works
end
處理錯誤的其他方法¶
儘管例外可用作處理錯誤的機制之一,但它們不是您唯一的選擇。拋出例外涉及分配記憶體,並且執行例外處理器通常很慢。
標準函式庫通常提供幾種方法來完成某些事情:一種會拋出,另一種會回傳 nil
。例如
array = [1, 2, 3]
array[4] # raises because of IndexError
array[4]? # returns nil because of index out of bounds
通常的慣例是提供一個替代的「問題」方法來表示此方法的變體回傳 nil
而不是拋出。這讓使用者可以選擇是要處理例外還是 nil
。然而,請注意,這並非適用於所有方法,因為例外仍然是首選方式,因為它們不會用錯誤處理邏輯污染程式碼。