2014-05-15
coderef を使うときの注意
まずは以下のソースを見て欲しい。
#!/usr/bin/env perl
use strict;
use warnings;
sub hoge {
die "hoge";
}
hoge({ return'hogehoge' });
これを実行するとどうなるかというと、
tsucchi@dev[1017]$ perl test.pl
Can't return outside a subroutine at test.pl line 9.
こんな感じでエラーになる。hoge の前に print などを入れると表示されることなどから、これはコンパイルエラーではなく、実行時エラーだと分かる。
では、この hoge()
を呼んでいる部分を eval
で囲むとどうなるだろうか?
ソース的には下記のような感じである。
#!/usr/bin/env perl
use strict;
use warnings;
sub hoge {
die "hoge";
}
eval {
hoge({ return'hogehoge' });
};
warn $@;
tsucchi@dev[1017]$ perl test.pl
Warning: something's wrong at test.pl line 12.
えーっと、die しない(12行目で「hoge」が出ない」ので、サブルーチンの実行自体がされていません。
その上 $@ にも入らなくてだいぶ残念な感じです。hoge
の後ろに print とか入れると実行されないので、
hoge
のところで eval
から return してるっぽい、ということが分かります。
って、なんでですかー(AA略
まー何が悪いって、そもそも意図していたコードは
hoge(sub { return'hogehoge' });
と、coderef なのに sub
を書き忘れた、って事なんですけどね。Try::Tiny とかの中でうっかり return
を書くとやばいアレと現象的には
同じものなのかな。
これの何がやばいって、Test::Class を使ったテストの中で、Test::Mock::Guard の劣化コピーみたいなのを使ってテスト書いているので、
sub test_hoge : Tests {
my ($self) = @_;
my $guard = stub_class('Hoge', { do_something => sub { return 'aaa' } });
...
}
みたいなのを山ほど書いてるんですが、うっかり typo というか sub
を書き忘れて、
my $guard = stub_class('Hoge', { do_something => { return 'aaa' } });
}
とか書いてしまう事がある。んで、冒頭の現象が発生してしまうと、$@
が空なので、テストが実行されずに意図せず成功してしまう。
(return
のところで return しちゃうので、後続の処理も行われない)。というとても悲しい感じになってしまうのです。
対策としては、
my $guard = stub_class('Hoge', { do_something => sub { 'aaa' } });
こんな感じで、
sub
をつけるreturn
書くのをやめる- Tests ではなく、Test(2) のようにテスト数をちゃんと書く
くらいしか思いつかなくて、わりと悲しい感じです。
2014-05-16 追記
id: hide_o_55 さんのブクマコメで指摘ありました(ご指摘どうもです)が、Tests の代わりにテスト数を書くってのもアリですね。めんどくさいから普段はやってないのですが。。。