오류와 오류 처리

오류와 오류 처리 (Errors and Error Handling)

Erlang 프로그램이 잘못된 동작을 하거나 예기치 않게 멈출 때, 그 이유를 정확히 아는 게 디버깅의 첫걸음이에요. 이번 장에서는 Erlang의 오류가 어떤 종류로 나뉘는지, 예외(exception)가 어떻게 표현되는지, 그리고 오류가 났을 때 프로세스가 어떻게 종료되는지 살펴볼게요.

출처: Erlang 공식 문서 - Errors and Error Handling

용어 (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 표현식은 구분하지 못합니다. trycatchExpressions에서 자세히 설명돼요.

클래스 기원
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은 꼬리 호출 최적화를 수행하는데, 이는 스택트레이스에 새 항목을 추가하지 않고 스택트레이스의 깊이도 제한합니다. 게다가 컴파일러 옵션이나 최적화, 향후 변경으로 스택트레이스 항목이 추가되거나 제거될 수 있어서, 스택트레이스가 특정 순서나 특정 항목을 갖고 있다고 기대하는 코드는 실패할 수 있어요.

이 규칙의 유일한 예외는 이유가 undeferror 클래스인데, 이 경우 시도된 함수의 Module, Function, Arity가 첫 번째 스택트레이스 항목으로 포함되는 것이 보장됩니다.

Erlang에서 런타임 오류 처리하기

프로세스 내 오류 처리

trycatch를 사용하면 런타임 오류나 다른 예외가 프로세스를 종료시키는 것을 막을 수 있어요.

프로세스 간 오류 처리

프로세스는 다른 프로세스를 모니터링하고 프로세스 종료를 감지할 수 있어요. 자세한 내용은 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가 아닌 것으로 평가되었어요.

  • noproc - 존재하지 않는 프로세스나 포트에 linkmonitor를 만들려고 했어요.

  • noconnection - 원격 프로세스에 대한 link나 monitor가, 노드 간 연결을 수립할 수 없거나 끊겨서 깨졌어요.

  • {nocatch,V} - catchtry/catch의 범위 밖에서 throw를 평가하려고 했어요. V는 던져진 항입니다.

  • system_limit - 시스템 한계에 도달했어요. 시스템 한계에 대한 자세한 내용은 Efficiency Guide의 System Limits를 참고하세요.

Stack은 오류가 발생했을 때 평가되고 있던 함수 호출들의 스택으로, 가장 최근의 함수 호출이 먼저 오는 {Module,Name,Arity,ExtraInfo} 튜플들의 목록으로 주어져요. 어떤 경우에는 가장 최근 함수 호출 튜플이 {Module,Name,[Arg],ExtraInfo}일 수도 있습니다.

더 알아보기 (Learn more)