Ruby에서 예외(Exception)는 특별한 개념이 아니라 그저 하나의 클래스입니다. 즉, 예외 계층 구조란 Exception 클래스를 상속하는 모든 클래스들의 집합이라고 할 수 있습니다.
다음은 Ruby 2.1 표준 라이브러리의 예외 계층 구조 전체 모습입니다.
Exception
NoMemoryError
ScriptError
LoadError
NotImplementedError
SyntaxError
SecurityError
SignalException
Interrupt
StandardError -- rescue의 기본값
ArgumentError
UncaughtThrowError
EncodingError
FiberError
IOError
EOFError
IndexError
KeyError
StopIteration
LocalJumpError
NameError
NoMethodError
RangeError
FloatDomainError
RegexpError
RuntimeError -- raise의 기본값
SystemCallError
Errno::*
ThreadError
TypeError
ZeroDivisionError
SystemExit
SystemStackError
실전에서의 활용
예외를 이렇게 클래스 트리 형태로 구성해 둔 이유는, 유사한 성격의 예외들을 한 번에 손쉽게 처리(rescue)할 수 있도록 하기 위함입니다.
예를 들어 다음 코드를 살펴보겠습니다.
begin
do_something
rescue StandardError => e
end
이 코드는 StandardError 자체뿐만 아니라, 그로부터 상속된 모든 예외를 함께 처리합니다. 실제로 우리가 일상적인 코드에서 관심을 가지게 되는 예외 대부분이 바로 이 StandardError 계열에 속합니다.
커스텀 예외 설계하기
직접 라이브러리나 애플리케이션을 작성할 때는, 자신만의 커스텀 예외들을 하나의 기본 클래스 아래에 두는 것이 좋은 설계 습관입니다.
module MyLib
class Error < StandardError
end
class TimeoutError < Error
end
class ConnectionError < Error
end
end
...
begin
do_something
rescue MyLib::Error => e
# 위에서 정의한 모든 예외를 한 번에 처리합니다
end
이렇게 하면 MyLib::TimeoutError, MyLib::ConnectionError 등 세부 예외를 각각 따로 처리할 수도 있고, 필요하다면 rescue MyLib::Error 한 줄로 라이브러리가 발생시키는 모든 예외를 일괄 처리할 수도 있습니다.
기억해 둘 핵심 포인트
rescue절에 예외 클래스를 명시하지 않으면 기본적으로StandardError가 적용됩니다.- 반대로
raise에 예외 클래스를 지정하지 않으면 기본값으로RuntimeError가 발생합니다. ScriptError(문법 오류, 로드 실패 등)나SystemExit같은 예외는StandardError를 상속하지 않으므로, 일반적인rescue로는 잡히지 않습니다. 이는 프로그램의 비정상 종료나 문법 오류를 무분별하게 삼키지 않도록 하기 위한 의도된 설계입니다.
예외 계층 구조를 제대로 이해하고 있으면, 불필요하게 넓은 범위의 예외를 잡는 실수를 피하고 더 안전하고 예측 가능한 코드를 작성할 수 있습니다.