오류와 오류 처리
오류와 오류 처리 (Errors and Error Handling)
Erlang 프로그램이 잘못된 동작을 하거나 예기치 않게 멈출 때, 그 이유를 정확히 아는 게 디버깅의 첫걸음이에요. 이번 장에서는 Erlang의 오류가 어떤 종류로 나뉘는지, 예외(exception)가 어떻게 표현되는지, 그리고 오류가 났을 때 프로세스가 어떻게 종료되는지 살펴볼게요.
용어 (Terminology)
오류는 대략 네 가지 유형으로 나눌 수 있어요:
-
컴파일 타임 오류(Compile-time errors) - 컴파일러가 프로그램을 컴파일하지 못할 때예요. 예를 들면 문법 오류가 있죠.
-
논리 오류(Logical errors) - 프로그램이 의도대로 동작하지 않지만 멈추지(crash)는 않는 경우를 말해요. 예를 들어 GUI의 버튼을 클릭했는데 아무 일도 일어나지 않는 경우가 있죠.
-
런타임 오류(Run-time errors) - 크래시가 발생하는 경우예요. 연산자가 잘못된 타입의 인자에 적용될 때가 예시입니다. Erlang에는 런타임 오류를 처리하는 기능이 내장되어 있어요.
error(Reason)을 호출해서 런타임 오류를 흉내 낼 수도 있습니다. 런타임 오류는 클래스error의 예외입니다. -
생성된 오류(Generated errors) - 코드 자체가
exit/1이나throw/1을 호출하는 경우예요. 생성된 오류는 클래스exit또는throw의 예외입니다.
Erlang에서 예외가 발생하면, 잘못된 표현식을 평가하던 프로세스의 실행이 멈춰요. 이를 실패(failure) 라고 하고, 실행이나 평가가 실패한다(fails) 거나 프로세스가 실패한다, 종료한다(terminates), 나간다(exits) 고 표현해요. 프로세스는 실패가 아닌 다른 이유로도 종료/종결될 수 있다는 점을 기억해 두세요.
종료하는 프로세스는 왜 종료했는지를 설명하는 종료 이유(exit reason) 와 함께 종료 시그널(exit signal) 을 발산해요. 보통은 잘못된 종료에 대한 정보가 터미널에 출력됩니다. 종료에 대한 자세한 내용은 Processes 장의 Process Termination에서 다룰게요.
예외 (Exceptions)
예외는 런타임 오류나 생성된 오류이며, 기원이 다른 세 가지 클래스가 있어요. try 표현식은 이 클래스들을 구분할 수 있지만, catch 표현식은 구분하지 못합니다. try와 catch는 Expressions에서 자세히 설명돼요.
| 클래스 | 기원 |
|---|---|
error |
런타임 오류. 예를 들어 1+a 또는 프로세스가 error/1을 호출한 경우 |
exit |
프로세스가 exit/1을 호출한 경우 |
throw |
프로세스가 throw/1을 호출한 경우 |
표: 예외 클래스들.
위 예외들은 모두 erlang:raise/3을 호출해서도 만들 수 있어요.
예외는 클래스, 종료 이유(Exit Reason 참조), 그리고 스택 트레이스(예외의 코드 위치를 찾는 데 도움을 주는 것)로 구성됩니다.
스택 트레이스는 모든 예외 클래스에 대해 try 표현식 안에서 변수에 바인딩할 수 있고, 런타임 오류가 catch에 잡혔을 때는 종료 이유의 일부로도 사용할 수 있어요. 예시:
> {'EXIT',{test,Stacktrace}} = (catch error(test)), Stacktrace.
[{shell,apply_fun,3,[]},
{erl_eval,do_apply,6,[]},
...]
> try throw(test) catch Class:Reason:Stacktrace -> Stacktrace end.
[{shell,apply_fun,3,[]},
{erl_eval,do_apply,6,[]},
...]
호출 스택 역추적 (stacktrace)
스택 역추적(stacktrace)은 {Module, Function, Arity, ExtraInfo} 및/또는 {Fun, Arity, ExtraInfo} 튜플을 담은 목록이에요. 튜플의 Arity 필드는 예외에 따라 아리티 정수 대신 그 함수 호출의 인자 목록일 수도 있습니다.
ExtraInfo는 (아마 비어 있는) 두 요소 튜플들의 목록으로, 어떤 순서로든 올 수 있으며 예외에 대한 추가 정보를 제공해요. 첫 번째 요소는 두 번째 요소에 있는 정보의 종류를 설명하는 원자입니다. 다음과 같은 항목이 나타날 수 있어요:
-
error_info- 튜플의 두 번째 요소는 예외의 원인이 무엇인지에 대한 추가 정보를 제공하는 맵이에요. 이 정보는error/3을 호출해서 만들 수 있고,erl_error:format_exception/4가 사용합니다. -
file- 튜플의 두 번째 요소는 함수의 소스 파일 이름을 나타내는 문자열(문자 목록)이에요. -
line- 튜플의 두 번째 요소는 예외가 발생했거나 함수가 호출된 소스 파일의 줄 번호(0보다 큰 정수)입니다.
경고 {: .warning }
개발자는 스택트레이스 항목을 디버깅 목적으로만 신뢰해야 해요.
VM은 꼬리 호출 최적화를 수행하는데, 이는 스택트레이스에 새 항목을 추가하지 않고 스택트레이스의 깊이도 제한합니다. 게다가 컴파일러 옵션이나 최적화, 향후 변경으로 스택트레이스 항목이 추가되거나 제거될 수 있어서, 스택트레이스가 특정 순서나 특정 항목을 갖고 있다고 기대하는 코드는 실패할 수 있어요.
이 규칙의 유일한 예외는 이유가
undef인error클래스인데, 이 경우 시도된 함수의Module,Function,Arity가 첫 번째 스택트레이스 항목으로 포함되는 것이 보장됩니다.
Erlang에서 런타임 오류 처리하기
프로세스 내 오류 처리
try나 catch를 사용하면 런타임 오류나 다른 예외가 프로세스를 종료시키는 것을 막을 수 있어요.
프로세스 간 오류 처리
프로세스는 다른 프로세스를 모니터링하고 프로세스 종료를 감지할 수 있어요. 자세한 내용은 Processes에서 다뤄요.
종료 이유 (Exit Reasons)
런타임 오류가 발생하면, 즉 클래스 error의 예외가 발생하면 종료 이유는 튜플 {Reason,Stack}이며, 여기서 Reason은 오류의 종류를 나타내는 항입니다:
-
badarg- 잘못된 인자. 인자가 잘못된 데이터 타입이거나, 그 외에 잘못된 형태로 되어 있어요. -
badarith- 산술 표현식의 인자가 숫자가 아니거나, 표현식이 유한한 숫자로 평가되지 않았어요. -
{badmatch,V}- 매칭 표현식의 평가가 실패했어요. 값V가 매칭되지 않았습니다. -
function_clause- 함수 호출을 평가할 때 매칭되는 함수 절이 없어요. -
{case_clause,V}-case표현식을 평가할 때 매칭되는 분기가 없어요. 값V가 매칭되지 않았습니다. -
if_clause-if표현식을 평가할 때 참인 분기가 없어요. -
{try_clause,V}-try표현식의 of-섹션을 평가할 때 매칭되는 분기가 없어요. 값V가 매칭되지 않았습니다. -
undef- 함수 호출을 평가할 때 함수를 찾을 수 없어요. -
{badfun,F}-F가 fun이어야 하는데 그렇지 않아요. -
{badarity,{Fun,Args}}- fun이 잘못된 개수의 인자에 적용되었어요. -
timeout_value-receive...after표현식의 타임아웃 값이 정수나infinity가 아닌 것으로 평가되었어요. -
noconnection- 원격 프로세스에 대한 link나 monitor가, 노드 간 연결을 수립할 수 없거나 끊겨서 깨졌어요. -
{nocatch,V}-catch나try/catch의 범위 밖에서throw를 평가하려고 했어요.V는 던져진 항입니다. -
system_limit- 시스템 한계에 도달했어요. 시스템 한계에 대한 자세한 내용은 Efficiency Guide의 System Limits를 참고하세요.
Stack은 오류가 발생했을 때 평가되고 있던 함수 호출들의 스택으로, 가장 최근의 함수 호출이 먼저 오는 {Module,Name,Arity,ExtraInfo} 튜플들의 목록으로 주어져요. 어떤 경우에는 가장 최근 함수 호출 튜플이 {Module,Name,[Arg],ExtraInfo}일 수도 있습니다.