Perlで例外をrethrowする

例外のキャッチ&スローに挑戦してみました。

野球とかそうだと思うのですが、
ゴロとか捕球したら、すぐさまファーストに投げなきゃいけないですよね?
今回はそういうの、まったく関係ないです。

という訳で、Exception::Tinyを使っていきたいと思います。

package MyException;
use parent 'Exception::Tiny';

package main;
use strict;
use warnings;
use v5.10;

use Try::Lite;

my $val = 0;

try {
    if ( $val == 0 ) {
        MyException->throw(
            message => 'oops!',
            value => $val
        );
    }
} (
    'MyException' => sub {
        say $@->dump;
    },
    '*' => sub {
        say $@;
    }
);

say '=== End of Script ===';

これを実行すると、こんな感じ。
$ perl aaa.pl
bless( {
'subroutine' => 'main::__ANON__',
'value' => 0,
'file' => 'aaa.pl',
'message' => 'oops!',
'package' => 'main',
'line' => 14
}, 'MyException' )

=== End of Script ===

なるほど、これは便利ですね。
例外送出時の状況が詳しく分かれば、デバッグが捗りますね。

ですが、関数の呼び出し元で例外を捕捉したい場合もあるので、
呼び出し先で例外を送出して、呼び出し元で捕捉してみたいと思います。

package MyException;
use parent 'Exception::Tiny';

package main;
use strict;
use warnings;
use v5.10;

use Try::Lite;

main();
say '=== End of Script ===';

sub main {
    my $val = 0;

    try {
        foo( 0 );
    } (
        'MyException' => sub {
            say $@->dump;
        },
        '*' => sub {
            say $@;
        }
    );
}

sub foo {
    my $val = shift;
    if ( $val == 0 ) {
        MyException->throw(
            message => 'oops!',
            value => $val
        );
    }
}

これを実行すると、
$ perl bbb.pl
bless( {
'subroutine' => 'main::foo',
'value' => 0,
'file' => 'bbb.pl',
'message' => 'oops!',
'package' => 'main',
'line' => 31
}, 'MyException' )

=== End of Script ===

呼び出した先で例外が起きても、これだけ情報が得られれば捗りますね。
続いては、キャッチ&スロー。

package MyException;
use parent 'Exception::Tiny';

package main;
use strict;
use warnings;
use v5.10;

use Try::Lite;

main();
say '=== End of Script ===';

sub main {
    try {
        foo( 'foo', 'foo', 'bar', 'foo' );
    } (
        'MyException' => sub { say $@->dump; },
        '*' => sub { say $@; }
    );
}

sub foo {
    say 'scalar(@_) = ', scalar(@_);
    my $arg = shift @_;
    try {
        if ( $arg ne 'foo' ) {
            MyException->throw( message => 'oops!' );
        }
    } (
        'MyException' => sub {
            say $@->dump;
            $@->{arg} = $arg; # デバッグ用に情報を追加
            $@->rethrow;
        }
    );

    if ( @_ ) {
        foo( @_ );
    } 
}

実行すると、こんな感じ。
$ perl ccc.pl
scalar(@_) = 4
scalar(@_) = 3
scalar(@_) = 2
bless( {
'subroutine' => 'main::__ANON__',
'file' => 'ccc.pl',
'message' => 'oops!',
'package' => 'main',
'line' => 27
}, 'MyException' )

bless( {
'subroutine' => 'main::__ANON__',
'arg' => 'bar',
'file' => 'ccc.pl',
'message' => 'oops!',
'package' => 'main',
'line' => 27
}, 'MyException' )

=== End of Script ===

こんな感じで、一旦捕捉して、
デバッグ用に情報を追加してから送出してる訳ですが、
このように再帰してても、上位で捕捉できるので便利ですね。

ところで、この例だとfooの中でMyExceptionしか捕捉してないけど、
MyException以外の例外はどうなるんですかね?

package MyException;
use parent 'Exception::Tiny';

package main;
use strict;
use warnings;
use v5.10;

use Try::Lite;

main();
say '=== End of Script ===';

sub main {
    try {
        foo();
    } (
        'MyException' => sub { say $@->dump; },
        '*' => sub {
            say sprintf("=== at line %d. ===", __LINE__);
            say $@;
        }
    );
}

sub foo {
    try {
        die 'oops!';
    } (
        'MyException' => sub { say $@->dump; }
    );
}

結果はこんな感じ。
$ perl ddd.pl
=== at line 20. ===
oops! at ddd.pl line 28.

=== End of Script ===

何のことはなかったですね、
無事、呼び出し元で例外を捕捉できました。

おしまい。

Leave a Comment