Exception behaviour
Exception behaviour
throw/catch/exit와 예외를 다루는 함수들을 제공하는 모듈이에요. 또한 커스텀 예외가 따라야 하는 behaviour도 여기서 정의해요. 나만의 예외를 정의하려면 defexception/1을 참고하면 돼요.
본문
포맷팅 함수
이 모듈에는 예외를 포맷해 주는 함수가 여러 개 있어요. 이 중 몇몇 함수는 인자로 **스택트레이스(stacktrace)**를 받아요. 스택트레이스는 보통 catch나 rescue 안에서 __STACKTRACE__/0 변수를 사용해 얻을 수 있어요.
다만 이 모듈의 함수가 돌려주는 특정 포맷에 의존하지 않는 게 좋아요. 그 포맷은 Elixir의 도구 체인에 더 잘 맞도록 미래 릴리스에서 바뀔 수 있거든요. 다시 말해, 이 모듈의 함수를 쓰면 지금 사용 중인 Elixir 버전의 방식으로 예외를 포맷한다는 것만 보장된다고 생각하면 돼요.
주요 타입
kind/0— 포맷팅 함수가 다루는 종류예요.:error이거나:exit,:throw,{:EXIT, pid}같은 비에러 종류예요.stacktrace/0— 스택트레이스 항목(stacktrace_entry/0)들의 리스트예요.t/0— 예외 타입이에요.
@type kind() :: :error | non_error_kind()
@type non_error_kind() :: :exit | :throw | {:EXIT, pid()}
@type stacktrace() :: [stacktrace_entry()]
@type stacktrace_entry() ::
{module(), atom(), arity_or_args(), location()}
| {(... -> any()), arity_or_args(), location()}
@type t() :: %{
:__struct__ => module(),
:__exception__ => term(),
optional(atom()) => any()
}
콜백
이 behaviour는 세 개의 콜백을 요구해요.
exception(term)—raise/2에 전달된 인자를 받아 예외 struct를 돌려줘요. 기본 구현은 struct에 병합되는 키워드 인자들의 집합이나, 예외 메시지로 쓸 문자열을 받아들여요.message(t)— 예외 struct를 받아 그 메시지를 돌려줘야 해요. 많은 예외가message필드를 갖고 있고 기본적으로 이 함수가 그 필드에 접근해요. 하지만message필드가 없는 예외라면 이 함수를 명시적으로 구현해야 해요.blame(t, stacktrace)— (선택)Exception.blame/3에서 예외 struct를 보강하기 위해 호출돼요. 예외에 대한 추가 정보를 모으거나, 비싼 추가 계산을 할 때 쓸 수 있어요.
@callback exception(term()) :: t()
@callback message(t()) :: String.t()
@callback blame(t(), stacktrace()) :: {t(), stacktrace()}
함수들
blame/3 — throw/error/exit에 디버깅을 위한 정보를 붙여줘요. 파일 시스템에서 데이터를 읽고, beam 파일을 파싱하고, 코드를 평가하는 등 비용이 꽤 들 수 있는 연산이에요. kind가 :error이고 오류가 Erlang 예외라면 정규화하고, Elixir 예외라면 예외 모듈의 선택 콜백인 blame/2를 구현했을 때 호출해요. message/1과 달리 이 함수는 에러를 구조하지 않아요. 콜백이 예외를 던지면 그 에러가 호출자에게 전파돼요.
blame_mfa/3 — 주어진 모듈·함수·인자의 호출을 blame해요. 바이트코드에서 사용 가능한 절들을 가져와 주어진 인자와 대조하고, {args, guards} 쌍의 리스트로 돌려줘요. {:ok, definition, clauses} 또는 :error를 돌려줘요. definition은 :def, :defp, :defmacro, :defmacrop 중 하나예요.
format/3 — throw/error/exit와 스택트레이스를 정규화하고 포맷해요. 최종 포맷은 format_banner/3과 format_stacktrace/1에 의존해요. kind가 {:EXIT, pid}이면 스택트레이스가 없는 메시지로 받아들이기 때문에 스택트레이스를 만들지 않아요.
format_banner/3 — 어떤 throw/error/exit든 정규화하고 포맷해요. Elixir CLI가 쓰는 것과 같은 포맷으로 메시지를 표시해요. 세 번째 인자인 스택트레이스는 정규화된 에러를 더 풍부하게 만들기 위해 쓰이며, kind가 에러일 때만 사용돼요.
format_exit/1 — exit를 포맷해서 문자열로 돌려줘요. exit 안에는 종종 에러/예외가 들어 있고, 호출자에 의해 감싸져 스택트레이스도 제공돼요. 이 함수는 exit 이유, 호출자, 스택트레이스를 잘 보여주도록 포맷해요.
format_fa/2 — 익명 함수와 arity를 받아 스택트레이스처럼 포맷해요. arity는 인자 리스트일 수도 있어요.
Exception.format_fa(fn -> nil end, 1)
#=> "#Function<...>/1"
format_file_line/3 — 파일과 줄 번호를 스택트레이스처럼 포맷해요. 값 중 하나가 nil이면 그 부분은 생략돼요.
iex> Exception.format_file_line("foo", 1)
"foo:1:"
iex> Exception.format_file_line("foo", nil)
"foo:"
format_file_line_column/4 — 파일, 줄, 열을 스택트레이스처럼 포맷해요. nil인 값은 생략돼요.
iex> Exception.format_file_line_column("foo", 1, 2)
"foo:1:2:"
iex> Exception.format_file_line_column("foo", nil, 2)
"foo:"
format_mfa/3 — 모듈, 함수, arity를 받아 스택트레이스처럼 포맷해요. arity는 인자 리스트일 수도 있어요.
iex> Exception.format_mfa(Foo, :bar, 1)
"Foo.bar/1"
iex> Exception.format_mfa(Foo, :bar, [])
"Foo.bar()"
익명 함수는 -func/arity-anonfn-count-로 표시되는데, 여기서 func는 익명 함수를 감싸고 있는 함수의 이름이에요. 이를 "anonymous fn in func/arity"로 변환해요.
format_stacktrace/1 — 스택트레이스를 포맷해요. 인자로 스택트레이스를 주거나, 주지 않으면 Process.info/2에서 가져와요.
format_stacktrace_entry/1 — 스택트레이스 항목 하나를 받아 문자열로 포맷해요.
message/1 — 예외의 메시지를 가져와요. 예외 모듈의 message/1 콜백을 호출해서 메시지를 얻어요. 만약 콜백이 예외를 던지거나 바이너리가 아닌 값을 돌려주면, 이 함수가 그 에러를 구조하고 대신 설명적인 에러 메시지를 돌려줘요.
normalize/3 — 예외를 정규화해서 Erlang 예외를 Elixir 예외로 변환해요. catch가 뱉어낸 종류를 인자로 받아 :error만 정규화하고, 나머지는 변경하지 않은 payload를 돌려줘요. 세 번째 인자인 스택트레이스는 kind가 에러일 때 정규화된 에러를 보강하는 데 쓰여요.
@spec normalize(:error, any(), stacktrace()) :: t()
@spec normalize(non_error_kind(), payload, stacktrace()) :: payload when payload: var
더 알아보기
- 커스텀 예외를 만드는 방법은
defexception/1문서를 참고해 보세요. raise/2와__STACKTRACE__/0의 동작을 함께 살펴보면 예외 흐름 전체가 이해돼요.- 예외 struct의 구조는
Kernel의 예외 관련 특수 폼 문서에서 더 자세히 확인할 수 있어요.