try .. rescue를 사용하면 블록 안에서 발생한 오류를 잡아서 처리할 수 있어요.
예를 들어 볼까요?
try do
raise RuntimeError, "error"
rescue
e in RuntimeError -> :error
end
try로 블록을 선언해요.raise/1을 호출하고 있어요.rescue 절에서 발생한 오류의 모듈 이름을 패턴 매칭해요.
-> 왼쪽에서는
e가 오류 구조체와 매칭돼요.in은 키워드예요.RuntimeError는 매칭할 모듈이에요. 하지만 어떤 오류 모듈과도 매칭할 수 있고, _를 쓰면 모든 오류와 매칭돼요.이렇게 rescue해서 잡은 오류는 (때때로 "예외"라고도 불러요) 구조체예요. 오류 구조체마다 키가 달라요. 표준 라이브러리의 "예외" 섹션에서 미리 정의된 모든 오류 목록을 볼 수 있어요.
# ArithmeticError caused by division by zero
%ArithmeticError{message: "bad argument in arithmetic expression"}
# Protocol.UndefinedError caused by passing `nil` to `Enum.count/1`
%Protocol.UndefinedError{description: "", protocol: Enumerable, value: nil}
Elixir에서 오류를 rescue하는 일은 아주 드물어요. 보통 rescue한 오류를 로그로 남기거나 외부 모니터링 서비스로 보낸 뒤, 다시 raise해요. 그래서 특정 오류 구조체의 내부 구조에는 보통 관심을 두지 않아요.
예외 개념에서는 직접 오류 구조체를 정의하는 방법을 설명해요.
오류로 논리 흐름을 제어하는 프로그래밍 패턴은 피해요. Elixir에서는 이런 방식이 안티 패턴이에요.
# Avoid using errors for control-flow.
try do
{:ok, value} = MyModule.janky_function()
"All good! #{value}."
rescue
e in RuntimeError ->
reason = e.message
"Uh oh! #{reason}."
end
# Rather, use control-flow structures for control-flow.
case MyModule.janky_function() do
{:ok, value} -> "All good! #{value}."
{:error, reason} -> "Uh oh! #{reason}."
end
Elixir 시작 가이드에 나와 있듯이:
[작업을 수행하는] 동안 발생한 오류가 예외적인지 아닌지는 애플리케이션이 결정할 일이에요. 그래서 Elixir는 [...] 함수에 예외를 강제하지 않아요. 대신 어떻게 진행하는 게 가장 좋을지는 개발자가 선택하도록 맡겨요.