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