Exception — 정상 흐름을 중단시키는 이상 사건
Exception — 정상 흐름을 중단시키는 이상 사건
프로그램이 뭔가 잘못됐을 때 그 사실을 알리는 방법이 예외(exception)예요. Raku에서 $! 변수(또는 CATCH 블록의 $_)에 들어가는 모든 예외는 Exception에서 상속돼요. die나 fail을 Exception이 아닌 인자와 함께 호출하면 그 값은 X::AdHoc 객체로 감싸지는데, 이것 역시 Exception을 상속받아요.
본문
class Exception {}
$! 변수(또는 CATCH 블록의 $_)에 들어가는 모든 예외는 Exception에서 상속돼요. die나 fail을 Exception이 아닌 인자와 함께 호출하면 그 값은 역시 Exception을 상속받는 X::AdHoc 객체로 감싸져요.
사용자 정의 예외 클래스도 Exception을 상속해야 하고, 최소한 method message를 정의해야 해요.
class X::YourApp::SomeError is Exception {
method message() {
"A YourApp-Specific error occurred: out of coffee!";
}
}
method message
method message(Exception:D: --> Str:D)
서브클래스가 반드시 덮어써야 하는 스텁(stub)이며, 예외 메시지를 반환해야 해요.
이 메서드가 그 자체로 예외를 만들어내지 않도록 특히 주의해야 해요.
try die "Something bad happened";
if ($!) {
say $!.message; # OUTPUT: «Something bad happened.»
}
method backtrace
method backtrace(Exception:D:)
예외와 연관된 백트레이스를 Backtrace 객체로, 없으면 빈 문자열로 반환해요. 적어도 한 번 던져진 예외에 대해서만 의미가 있어요.
try die "Something bad happened";
with $! { .backtrace.print ; }
method throw
method throw(Exception:D:)
예외를 던져요.
my $exception = X::AdHoc.new; # Totally fine
try $exception.throw; # Throws
if ($!) { #`( some handling ) }; # Suppress the exception
method resume
method resume(Exception:D:)
CATCH 블록에서 처리될 때 .throw가 멈춘 곳으로 제어 흐름을 다시 이어가요.
# For example, resume control flow for any exception
CATCH { default { .resume } }
method rethrow
method rethrow(Exception:D:)
적어도 한 번 던져진 예외를 다시 던져요. 원래 백트레이스를 보존한다는 점에서 throw와 달라요.
sub f() { die 'Bad' };
sub g() { f; CATCH { default { .rethrow } } };
g;
CATCH { default { say .backtrace.full } };
routine fail
multi fail(Exception $e)
method fail(Exception:D:)
호출한 루틴을 빠져나가고, 그 예외를 감싼 Failure 객체를 반환해요.
# A custom exception defined
class ForbiddenWord is Exception {
has Str $.word;
method message { "This word is forbidden: «$!word»" }
}
sub say-word ( $word ) {
ForbiddenWord.new(:word($word)).fail if $word eq 'foo';
$word.say;
}
my $result = say-word("foo");
say $result.exception;
루틴 형식도 마찬가지로 동작해요. 대안 문법은 fail ForbiddenWord.new(:word($word))예요.
method gist
multi method gist(Exception:D:)
이 예외에 대해 예외 프린터가 만들어야 하는 것을 반환해요. 기본 구현은 message와 backtrace를 새 줄로 구분해 반환해요.
my $e = X::AdHoc.new(payload => "This exception is pretty bad");
try $e.throw;
if ($!) { say $!.gist; };
# OUTPUT: «This exception is pretty bad
# in block <unit> at <unknown file> line 1»
method Failure
method Failure(Exception:D: --> Failure:D)
Rakudo 컴파일러 2022.06 릴리스부터 사용할 수 있어요.
Exception을 Failure 객체로 강제 변환해요.
routine die
multi die()
multi die(*@message)
multi die(Exception:D $e)
method die(Exception:D:)
치명적인(fatal) Exception을 던져요. 기본 예외 처리기는 리스트의 각 요소를 $*ERR(STDERR)로 출력해요.
die "Important reason";
서브루틴 형식을 인자 없이 호출하면 $! 변수의 값이 검사돼요. $!이 .DEFINITE 값으로 설정되어 있고 그 값이 Exception 타입이면 그 값이 던져질 Exception으로 쓰이고, 아니면 X::AdHoc 예외의 payload로 쓰여요. $!이 .DEFINITE가 아니면 payload가 "Died" 문자열인 X::AdHoc이 던져져요.
die는 기본적으로 발생한 줄 번호를 출력해요.
die "Dead";
# OUTPUT: «(exit code 1) Dead
# in block <unit> at /tmp/dead.raku line 1»
하지만 그 기본 동작은 Exception 수준에서 정해지므로, CATCH로 예외를 잡아 원하는 대로 바꿀 수 있어요. 예를 들어 줄 번호를 숨기는 데 쓸 수 있어요.
CATCH {
default {
.payload.say
}
};
die "Dead" # OUTPUT: «Dead»
sub warn
multi warn(*@message)
재개 가능한(resumable) 경고 예외를 던져요. 경고는 제어 예외로 간주되어 대부분의 일반 예외 처리기에 보이지 않아요. 가장 바깥쪽 제어 처리기가 경고를 $*ERR로 출력해요. 경고를 출력한 뒤 예외는 던져진 지점에서 재개돼요. 이 동작을 덮어쓰려면 CONTROL 블록에서 예외를 잡아야 해요. quietly {...} 블록은 try {...} 블록의 반대라서, 경고는 억제하되 치명적인 예외는 그대로 통과시켜요.
단순히 $*ERR로 출력하고 싶다면 note를 쓰세요. warn은 예외까지 던지긴 싫지만 위협적 상황에서 쓸 용도로 아껴두는 게 좋아요.
warn "Warning message";