錯誤難免會發生。在 Elixir 裡,雖然大家常說「讓它崩潰就好」,但有時候我們仍需要把函式呼叫救回已知的良好狀態,以滿足軟體合約。在某些語言裡,錯誤被當成控制流程的一種手段,但在 Elixir 裡並不鼓勵這種模式。我們通常光看名稱就能認出哪些函式可能會拋出錯誤:會拋出錯誤的函式,名稱結尾帶有!。這是相對於會回傳{:ok, value}或:error的函式。看看這些函式庫範例:
Map.fetch(%{a: 1}, :b)
# => :error
Map.fetch!(%{a: 1}, :b)
# => raises KeyError
Elixir 提供了一種用try .. rescue從錯誤中救回的建構。
try do
raise RuntimeError, "error"
rescue
e in RuntimeError -> :error
end
我們來看看這個建構:
try宣告這個程式區塊。raise/2。rescue區段裡,我們對所拋出錯誤的_模組_名稱做模式配對
->的左側:
e 會配對到錯誤結構。in 是關鍵字。RuntimeError 是我們想救回的錯誤。
_代替模組名稱,或完全省略in這個關鍵字。用這種方式救回的錯誤(有時也稱為「例外」)是結構。 在 Elixir 裡,救回錯誤的情況非常少見。 通常救回的錯誤會被記錄下來,或送到外部的監控服務,然後再重新拋出。 這表示我們通常不太在意特定錯誤結構的內部結構。
在例外概念中,你會學到更多關於錯誤結構的內容,包括如何定義自己的自訂錯誤。
在 Instruments of Texas 工作時,你的任務是開發一個以 Elixir 撰寫的實驗性逆波蘭表示法 [RPN] 計算機。你的團隊遇到了某些操作會引發錯誤、導致行程崩潰的問題。你的任務是撰寫一個函式來包裝操作函式,讓錯誤能用更優雅、更符合 Elixir 慣用寫法的方式處理。
實作函式calculate!/2,以堆疊作為唯一的引數來呼叫操作函式。這個操作函式定義在別處,但你知道它可能順利完成,也可能引發錯誤。
stack = []
operation = fn _ -> :ok end
RPNCalculator.calculate!(stack, operation)
# => :ok
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate!(stack, operation)
# => ** (ArgumentError) An error occurred
函式名稱以
!結尾,是在警告程式設計師這個函式可能會引發錯誤
在做更多研究時,你注意到許多函式會用原子和元組來表示成功或失敗。請用這個策略來實作calculate/2。
stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate(stack, operation)
# => {:ok, "operation completed"}
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate(stack, operation)
# => :error
有些錯誤含有重要資訊,你的同事必須知道這些資訊,才能確保系統正確運作。實作calculate_verbose/2來傳遞錯誤訊息。這個錯誤是一個 struct,其中帶有:message欄位。
stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:ok, "operation completed"}
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:error, "An error occurred"}