Exception — 정상 흐름을 중단시키는 이상 사건

Exception — 정상 흐름을 중단시키는 이상 사건

프로그램이 뭔가 잘못됐을 때 그 사실을 알리는 방법이 예외(exception)예요. Raku에서 $! 변수(또는 CATCH 블록의 $_)에 들어가는 모든 예외는 Exception에서 상속돼요. diefailException이 아닌 인자와 함께 호출하면 그 값은 X::AdHoc 객체로 감싸지는데, 이것 역시 Exception을 상속받아요.

출처: Raku Documentation — Exception

본문

class Exception {}

$! 변수(또는 CATCH 블록의 $_)에 들어가는 모든 예외는 Exception에서 상속돼요. diefailException이 아닌 인자와 함께 호출하면 그 값은 역시 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 릴리스부터 사용할 수 있어요.

ExceptionFailure 객체로 강제 변환해요.

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";

출처: Raku Documentation — Exception