Carpは継承関係のモジュールを信頼する

初めに

以下のドキュメントにCarpの詳細説明があるので、それを読めば挙動が分かります。
Carp - モジュールのための warn と die の代替

この記事では、実際に継承してるコードを例に出して説明しようと思います。

コードと実行結果

client.pl

#!/usr/bin/env perl
use strict;
use warnings;
use Concrete;

Concrete->new->run(@ARGV);

Concrete.pm(具象クラス)

package Concrete;
use strict;
use warnings;
use Carp;
use parent 'Abstract';

sub speak {
    my $self = shift;
    croak "ダレカタスケテー";
}

1;

Abstract.pm(抽象クラス)

package Abstract;
use strict;
use warnings;

sub new {
    my $class = shift;
    bless {}, $class;
}

sub run {
    my $self = shift;
    $self->speak;
}

sub speak {}

1;

実行結果

$ ./client.pl
ダレカタスケテー at ./client.pl line 6.

挙動について

このコードを実装した際、呼び出し元がAbstract::runなので、
ダレカタスケテー at Abstract.pm line 12. と出るかと思ったら、
client.plの行数が出てしまいました。

これは何故かというと、継承関係にある呼び出し元は(具体的には@ISAに入っていれば)、
安全であると判断されスルーされます。
更に上位の呼び出し元はclient.plになるので、上記の実行結果となりました。

これだとエラーとしては分かり辛いなぁと思います。

対策

dieを使う

dieを実行した行数とそのファイル名を返します。

$ ./client.pl
ダレカタスケテー at Concrete.pm line 10.

confessを使う

confessスタックトレースを伴うエラーを発生させます。

$ ./client.pl
ダレカタスケテー at Concrete.pm line 10.
    Concrete::speak('Concrete=HASH(0x177b178)') called at Abstract.pm line 12
    Abstract::run('Concrete=HASH(0x177b178)') called at ./client.pl line 6

Carp::verboseを有功にする

全てのcroakconfessに、carpcluckにとして扱うようにします。
CPANからダウンロードして使用している外部モジュールなども対象となります。

$ perl -MCarp=verbose client.pl
ダレカタスケテー at Concrete.pm line 10.
    Concrete::speak('Concrete=HASH(0x184b1a8)') called at Abstract.pm line 12
    Abstract::run('Concrete=HASH(0x184b1a8)') called at client.pl line 6

まとめ

Template Methodパターンで作ってたらこの問題にぶち当たりました。
CPANに公開しないモジュールであれば、confessを使っておくのが良いかなぁと思ってます。
dieは情報が少ないし、Carp::verboseはやりすぎ感がある……)

おまけ

evalで例外をキャッチするとこんな表示になります。

Abstract.pm(runを書き換え)

sub run {
    my $self = shift;
    eval { $self->speak };
    if($@) {
        print "$@";
        warn "チョットマッテテー";
    }
}

実行結果

Use of uninitialized value $_ in string at Abstract.pm line 14.
ダレカタスケテー at Concrete.pm line 10.
    Concrete::speak('Concrete=HASH(0x1ec0178)') called at Abstract.pm line 12
    eval {...} called at Abstract.pm line 12
    Abstract::run('Concrete=HASH(0x1ec0178)') called at ./client.pl line 6
チョットマッテテー at Abstract.pm line 15.

eval {...} が追加されてますね!
やりたかっただけです!