2007年02月11日 13:45 [Edit]

perl - B::Deparse

camel

尻馬乗るべし、ということでB::Deparseの紹介。

いやなブログ - スクリプト言語用のデバッガの使い方 - Ruby, Python, Perl
スクリプト言語用の CUIのデバッガの使い方を簡単にまとめました。対象言語は Ruby, Python, Perl です。

実は私も、デバッガーはperl -de1ぐらいしか使っていない(perl -de1は非常によく使うので、Terminal.appのウィンドウの一つがそれ専用になっている。スクリプト言語のインタラクティブな利用法に関しては以前「404 Blog Not Found:LL Intaractive」にまとめたのでそちらをご覧頂くとして、ここではなぜスクリプト言語では滅多にデバッガーを使わないかをおさらいした上で、B::Deparseを取り上げる。

私は C, C++ でプログラムを書いているときはデバッガ (主に GNU/Linux 上の gdb) を頻繁に利用します。しかし、スクリプト言語ではそれほどでもありません。これはおそらく次のような理由によります。

  • ビルドが不要なので printf デバッグが容易 (ある程度大きい C++ のプログラムではビルド時間が長いので printf の挿入はしんどい)
  • 異常終了時にスタックトレースが表示される (Ruby, Python なら自動、Perl の場合は use Carp; $SIG{__DIE__} = \&Carp::confess; など)
  • オブジェクトのインスペクトが簡単 (Ruby なら p/pp, Python なら print, Perl なら Data::Dumper)
  • セグメンテーションフォルトは (基本的に) 発生しない
  • 小さい単純なプログラムを書いている場合が多い

Perlの場合、これに是非「B::Deparseがあるから」というものを加えたい。それくらい便利なツールである。

利用法その一:スクリプト全体をDeparse

Perlの悪名の理由の一つは、「書きやすいが読みにくい」というものであった。実際そうでもないし、昨今、特に「Perl Best Practices」が上梓されてからはblogosphereを賑わすcode snippetsの可読性もみちがえるように上がったのだが、それでも一行野郎(one-line)などは読みやすさは2の次3の次だし、golfやObfuscnated Codesなど、わざと読みにくくしているものもある。

そんな時は、B::Deparseにそのコードを読み解いてもらおう。使い方は簡単。

perl ...

としていたところを、

perl -MO=Deparse ...

とすればよい。-MB=Deparseでない点に注意。そのコードが実行される代わりに、そのコードがPerlから見てどう見えたかが表示される。

例えば、「ファイル中に登場する0.01をすべて0.02に書き換えて、元のファイルをバックアップする」という作業なら、

perl -i.bak -ple 's/0\.01/0.02/g' files ...

と書くが、これに-MB=Deparseをふりかけるとこう表示される。

% perl -MO=Deparse -i.bak -ple 's/0\0.1/0.02/g'
BEGIN { $^I = ".bak"; }
BEGIN { $/ = "\n"; $\ = "\n"; }
LINE: while (defined($_ = )) {
    chomp $_;
    s/0\0.1/0.02/g;
}
continue {
    print $_;
}
-e syntax OK

「いやなブログ」のデバッギ(debugee - これ、バッドノウハウ英語で紹介してもいいのでは)も、手だれのPerl Mongerならこうしているだろう。

perl -MLWP::Simple -e 'print get shift' http://0xcc.net

これも、-MB=Deparseで「元に戻せ」る。

% perl -MO=Deparse -MLWP::Simple -e 'print get shift' http://0xcc.net
use LWP::Simple;
print get(shift @ARGV);
-e syntax OK

表示を微調整することも出来る。例えばこんな風に。

% perl -MO=Deparse,-l,-x7 -MLWP::Simple -e 'print get shift' http://0xcc.net

#line 0 "-e"
sub BEGIN {
#line 0 "-e"
    require LWP::Simple;
#line 0 "-e"
    do {
#line 0 "-e"
        'LWP::Simple'->import
    };
}
#line 1 "-e"
print get(shift @ARGV);
-e syntax OK

詳しくは例によってperldoc B::Deparseを参照のこと。B::Deparseは初心者の味方である。

クロージャーをディスクローズ

中級以上のPerl Programmerは、今やcode referenceの利用が日常茶飯事である。非常に強力なのだが、欠点としてデバッグしにくいということがある。ものによっては動的にcodeを生成するので、本当に実行されるコードはどうなのか、ソースを見ても見えないのだ。

こういう時こそ、B::Deparseの出番である。

たとえば、以下のようなコードがあるとする。

   1 : use strict;
   2 : use warnings;
   3 : {
   4 :     package MyFileHandle;
   5 :     use base 'FileHandle';
   6 :     sub each_line {
   7 :         my $fh      = shift;
   8 :         my $coderef = shift;
   9 :         while ( my $line = $fh->getline ) {
  10 :             $coderef->( $fh, $line );
  11 :         }
  12 :     }
  13 : }
  14 : my $fh = MyFileHandle->new( shift, 'r' ) or die;
  15 : $fh->each_line(
  16 :     sub {
  17 :         my ( $fh, $line ) = @_;
  18 :         printf "%4d : %s", $fh->input_line_number, $line
  19 : 
  20 :     }
  21 : );

まず、ここでeach_lineの方でどんなcode referenceを受け取ったかを確認してみたいものとする。こうすればよい。

   3 : {
   4 :     package MyFileHandle;
   5 :     use base 'FileHandle';
   6 :     use B::Deparse;
   7 :     sub each_line {
   8 :         my $fh      = shift;
   9 :         my $coderef = shift;
  10 :         my $bd      = B::Deparse->new;
  11 :         warn $bd->coderef2text($coderef);
  12 :         while ( my $line = $fh->getline ) {
  13 :             $coderef->( $fh, $line );
  14 :         }
  15 :     }
  16 : }

すると、each_lineが呼ばれる都度

{
    use warnings;
    use strict 'refs';
    my($fh, $line) = @_;
    printf '%4d : %s', $fh->input_line_number, $line;
} at deparse.pl line 11.

と出てくる。

メソッドをディスクローズ

上記の例で、逆にeach_line()の中身を呼び出し側で確認したい場合はどうしたらよいか?

こうする。

  15 : my $fh = MyFileHandle->new( shift, 'r' ) or die;
  16 : use B::Deparse;
  17 : my $bd = B::Deparse->new();
  18 : warn $bd->coderef2text($fh->can('each_line'));

実行されると、こうなる。

{
    package MyFileHandle;
    use warnings;
    use strict 'refs';
    my $fh = shift @_;
    my $coderef = shift @_;
    while (my $line = $fh->getline) {
        &$coderef($fh, $line);
    }
} at deparse.pl line 18.

これを使えば、デバッガーを使わずともメソッドのコードを覗く事ができるのだ。

Data::DumperやStorableと組み合わせて

実はData::DumperStorableも、B::Deparseをサポートしている。これらを利用すると、code referenceを普通のオブジェクトのように扱ったり、シリアライズしたりすることが簡単に出来る。詳しくはそれぞれのマニュアルを参照のこと。

まとめ

以上のように、B::Deparseは初心者から上級者まで、実に使い出のあるモジュールである。これのおかげでかつてはデバッガに頼っていたこともこれでほとんど間に合うようになった。RubyやPythonではどうしているのだろうか....

Dan the Perl Monger


この記事へのトラックバックURL

この記事へのトラックバック
perlでプログラムデバック時に Data::Dumper を使って変数内のデー...
Data::Dumper を使ってデータ構造を覗き見る【@OMAKASE】at 2012年02月29日 14:08
こういう時に便利な標準モジュール、B::Conciseを紹介します。 YappoLogs: CPUの気持ちは大事だけど、VMの気持ちも考えようよブロックがあるという事は、スコープがあると同義なのは当たり前ですが、スコープが変わるという事はレキシカル変数の処理などをやらなき....
perl - B::Concise で VM の気持ちに触れる【404 Blog Not Found】at 2009年03月27日 19:34
NANOChatのMENTA化がなかなかうまくいかない。 なんというか、グローバ...
Class::Accessor::Fastを触ってみる【日曜プログラマのそゞろ事】at 2009年01月07日 01:45
というわけで 404 Blog Not Found perl - B::Deparse B::Deparseというモジュールがすごく便利だということがわかったわけですが、気になったところが。 実はData::DumperもStorableも、B::Deparseをサポー...
B::Deparse + Storable【pepepeのblog】at 2007年02月11日 16:15
誰がどう見てもmixi系のツールで最強です。 これはmixiで想像出来る事を全て自動化できる凄いツールです。
【近日値上げ決定】【今までのツールは何!?ミクシィツールの最終最強版・遂に登場!】迷宮のようなミクシィの中を快適に探検する為の必須アイテム!『 ミクシィクエスト / mixiQuest 』【ネットで本当に稼げた月30万円!】at 2007年02月11日 14:04