目次


洗練されたPerl: 楽々Perlデバッグ

噛み付かれる前にバグを取る

Comments

バグは、死や税金と同様、避けられないものです。とは言え、バグの落とし穴を避ける点で、以下の記事は助けになるに違いありません。いくつかの例では、Perl 5.6.0、あるいは最低でも5.005が必要となります。Emacsの例を試してみたいと思われる場合は、Emacsエディターをインストールすることも必要になるかもしれません。

バグに伴う困難

ソフトウェア・デベロッパーはえてして、ソフトウェアのテストの重要性を過小評価するものです。その主な理由は単純です。つまり、バグの処理は大変だということです。しばしば、バグのために、デベロッパーがプロジェクトの主な部分を一から書き直さなければならなくなることさえあるのです。と言うのは、バグは、コードの基本的な欠陥を明らかにするのが得意だからです。

デバッグは非常に重要なので、プロジェクトの予定表の中で、最低30%はデバッグに割り振るべきだというのが私の考えです。デバッグに余分の時間をさけば、製品はもっと良くなります。逆に、ソフトウェアの納入を早めようとしてデバッグの時間を削減するなら、後になって明るみに出る問題の修正のために、実動開始後に費やす時間が倍増するのはほぼ間違いありません。

基本的に、バグには3種類あります。すなわち、コーディングのバグ、ドキュメンテーションのバグ、そして要件のバグです。要件のバグは、一般に、要件が不正確であるか、欠落しているために生じます。ドキュメンテーションのバグは、マニュアルまたはオンライン・ヘルプの中に含まれているものです。コーディングのバグは、プログラマーが要件をインプリメントするときに冒す誤りが原因で生じます。残念ながら、要件のバグとドキュメンテーションのバグは、ここで扱う範囲を超えています。したがって、ここでは、コーディングのバグの「検出」、「解決」、および「修正」に限って考慮します。

デバッグの基本概念

コーディングのバグとは、プログラマーが要件をインプリメントするときに冒す誤りであると定義しました。コーディングのバグは、不適切なプログラムの振る舞い (要件から外れた振る舞い) につながります。したがって、プログラマーがプログラムの作成またはデバッグを始める前にまず知っておかなければならないことは、プログラムの要件です。

デバッグは、ある意味で狩猟に似ています。最初のステップは、(間違った振る舞いを観察したり、パターンを識別したりすることによって) バグを検出することです。この段階では、バグは単なる徴候に過ぎません。

2番目のステップは、バグを解決することです。プログラムをよく知っている人がバグを調べ、その根本的原因を理解するようにすべきです。なぜなら、バグはその根源から絶たれなければならないからです。コードが理解しやすくなり、バグがあったバージョンよりもコードの量が増えていなければ、いい線を行っていると言えるでしょう。

3番目、つまり最後のステップは、バグを修正することです (「修正」が「解決」と区別 されていることに注意してください)。デバッグの実行者はソース・コードの変更を「本物」の実動環境に移し、その変更が正しいかどうかを検査します。コードが正しくない場合、それは、バグの解決に失敗したか、一層悪いことに、新しいバグが生じたかのいずれかです。バグを修正して、結局のところ新しいバグを作っただけだったというのでは面 白くありませんから、バグを解決するたびにそれを確実に修正するようにしてください。

バグをすばやく見つけ、それらのバグをよく理解するためには、プログラムのアクションに関する明確なイメージを、主な分岐すべてに渡って頭の中に描くことが必要です。すべてのモジュールとクラスも含め、そのイメージをデバッグのプロセスの間ずっと保たなければなりません。これは、当然のこととして、コードの作成言語 (この場合はPerl) を明確に理解していなければならないということを暗に意味します。こうしたすべての要件を考えると、優れたソフトウェア・テスターは希少です。

Perlデバッガー

Perlのプログラマーが最初に頼るのは、Perlに付属のデバッガーです。お気付きになると思いますが、これはいきなり使い始めることができるほど簡単なものです。

デバッガーを使ってスクリプトを実行する

perl -d program.pl

Perlデバッガーには専用のヘルプが付属しています (項目の多いヘルプ画面の場合は 'h'、項目の少ないヘルプ画面の場合は 'h h')。perldoc perldebugページ (プロンプトで "perldoc perldebug" と入力する) には、Perlデバッガーに関するより詳細な説明が記載されています。

それでは、バグのあるプログラムを使って、Perlデバッガーがどのように機能するかを確かめてみましょう。まず、このプログラムは、あるファイルの最初の20行を印刷しようとします。

buggy.pl
 #!/usr/bin/perl -w
 use strict;
 foreach (0..20)
 {
  my $line =<>;
  print "$_ : $line";
 }

buggy.pl自体を実行すると、"Use of uninitialized value in concatenation (.) at ./buggy.pl line 8,<> line 9" というメッセージを出して、プログラムは失敗します。さらに不可解なことに、自分自身で行上に "9:" と印刷し、ユーザー入力を待ちます。

これはどういうことでしょうか。Perlデバッガーを起動したときに現れた悩みの種の正体を、既に見抜いておられるかもしれません。

まず、単にバグが再現可能かどうかを調べてみましょう。エラーの発生した8行目に $lineを印刷するためのアクションを設定し、プログラムを実行します。

buggy.plデバッガー・コマンド
 > perl -d ./buggy.pl buggy.pl
 Default die handler restored.
 Loading DB routines from perl5db.pl version 1.07
 Editor support available.
 Enter h or `h h' for help, or `man perldebug' for more help.
 main::(./buggy.pl:5):   foreach (0..20)
 main::(./buggy.pl:6):   {
   DB<1> use Data::Dumper
   DB<2> a 8 print 'The line variable is now ', Dumper $line

Data::Dumperモジュールをロードして、autoactionが整った出力フォーマットを使用できるようにします。autoactionは、8行目に到達するたびにprintステートメントを実行するように設定されています。早速、様子を見てみましょう。

buggy.plデバッガー・コマンド (その2)
  DB<3> c
 The line variable is now $VAR1 = '#!/usr/bin/perl -w
 ';
 0 : #!/usr/bin/perl -w
 The line variable is now $VAR1 = '
 ';
 1 : The line variable is now $VAR1 = 'use strict;
 ';
 2 : use strict;
 The line variable is now $VAR1 = '
 ';
 3 : The line variable is now $VAR1 = 'foreach (0..20)
 ';
 4 : foreach (0..20)
 The line variable is now $VAR1 = '{
 ';
 5 : {
 The line variable is now $VAR1 = ' my $line =<>;
 ';
 6 :  my $line =<>;
 The line variable is now $VAR1 = ' print "$_ : $line";
 ';
 7 :  print "$_ : $line";
 The line variable is now $VAR1 = '}
 ';
 8 : }
 The line variable is now $VAR1 = undef;
 Use of uninitialized value in concatenation (.) at ./buggy.pl line 8, <> line 9.
 9 :

これで、行変数が未定義だった場合に問題が発生したことがはっきりしました。その上、プログラムはさらに入力を待っています。リターン・キーをあと11回押すことにより、以下の出力が得られました。

buggy.plデバッガー・コマンド (その3)
 The line variable is now $VAR1 = '
';
10 :
The line variable is now $VAR1 = '
';
11 :
The line variable is now $VAR1 = '
';
12 :
The line variable is now $VAR1 = '
';
13 :
The line variable is now $VAR1 = '
';
14 :
The line variable is now $VAR1 = '
';
15 :
The line variable is now $VAR1 = '
';
16 :
The line variable is now $VAR1 = '
';
17 :
The line variable is now $VAR1 = '
';
18 :
The line variable is now $VAR1 = '
';
19 :
The line variable is now $VAR1 = '
';
20 :
Debugged program terminated.  Use q to quit or R to restart,
  use O inhibit_exit to avoid stopping after program termination,
  h q, h R or h O to get additional info.  DB<3>

ここまでで、このプログラムにはバグがあることがはっきりしました。なぜなら、行がない場合があるにもかかわらず、無条件に20行の入力を待つからです。修正法は、<> ファイル・ハンドルから $lineを読んだ後で、それをテストすることです。

修正版のbuggy.pl
#!/usr/bin/perl -w
 use strict;
 foreach (0..20)
{
  my $line =<>;
 last unless defined $line;		# exit loop if $line is not defined
 print "$_ : $line";
}

お気付きのとおり、修正版のプログラムはすべての場合において正しく動作します!

Perlデバッガーに関する結びの注記

EmacsエディターはPerlデバッガーをサポートしており、デバッガーをいくぶん使いやすくしてくれます。GUD Emacsモードについては、Infoを使ってEmacsの中からさらに詳しく調べることができます (M-x infoと入力)。GUDは汎用のデバッグ・モードであり、Perlデバッガーと一緒に機能します (EmacsでPerlプログラムを編集している間に、M-x perldbと入力)。

あまり役に立たないとはいえ、viファミリーのエディターもPerlデバッガーをサポートすることでしょう。詳細については、perldoc perldebugページを参照してください。その他のエディターについては、それぞれのエディターの資料を参照してください。

Perlの組み込み (標準装備の) デバッガーは強力なツールであり、上に示した単純な使用法をはるかに超えたことを行うことができます。とはいえ、このデバッガーを使用するには、Perlに関する相当の知識が必要です。それで、より単純なツールについてこれから調べることにします。このツールは、初級および中級のPerlプログラマーにより適したものです。

Devel::ptkdb

Devel::ptkdbデバッガーを使用するには、まずこれをCPAN (以下の「参考文献」を参照) からダウンロードし、システムにインストールする必要があります。(Tkモジュールのインストールが必要な場合もありますが、これもCPANから入手できます。) 個人的な覚え書きですが、Devel::ptkdbは、LinuxのようなUNIXシステム上で最も良く機能します。(理論上、Devel::ptkdbはUNIX互換のシステムだけに限定されているわけではありませんが、これをWindows上で使用することに成功した人の話はまったく聞いたことがありません。古い言い回しの述べるとおり、回転ドアをスキーで通り抜けることを除けば、どんなことでも可能なのです。)

システム管理者にインストールを依頼できない (たとえば、あなた自身 がシステム管理者であるなどの理由で) のであれば、プロンプトから次の内容を実行してみることができます (これはルートとして実行する必要があるかもしれません)。

CPANからDevel::ptkdbをインストールする
perl -MCPAN -e'install Tk'
perl -MCPAN -e'install Devel::ptkdb'

最初にいくつかの質問が出されます。CPANインストール・ルーチンを初めて実行する場合であれば、その後、適切なモジュールのダウンロードとインストールが自動的に行われることになります。

ptkdbデバッガーを使ってプログラムを実行するには、次のようにします (前述のbuggy.plの例を使用します)。

Devel::ptkdbの使用
perl -d:ptkdb buggy.pl buggy.pl

Devel::ptkdbモジュールの資料を読むには、"perldoc Devel::ptkdb" というコマンドを使用します。ここで使用しているバージョンは1.1071です。(更新バージョンが随時公開されますが、外観はここで使用するものとそれほど変わらないはずです。)

左側にはプログラムのソース・コードを表示したウィンドウが、右側には監視される式のリスト (最初は空) が表示されます。「Enter Expr: (式入力)」ボックスに "$line" という語を入力します。それから、「Step Over (ステップオーバー)」ボタンをクリックして、プログラム実行を監視します。

「Run (実行)」ボタンをクリックすると、プログラムは終わりまで実行されるか、ブレークポイントに到達します。ソース・リスト・ウィンドウで行番号をクリックすると、ブレークポイントを設定または削除できます。右側の「BrkPts (ブレークポイント)」タブを選択すると、ブレークポイントのリストを編集したり、変数または関数による条件を設定したりすることができます。(これは、条件付きブレークポイントをセットアップするための非常に簡単な方法です。)

Ptkdbには、「File (ファイル)」、「Control (制御)」、「Data (データ)」、「Stack (スタック)」、および「Bookmarks (ブックマーク)」の各メニューもあります。これらのメニューはどれも、perldoc文書の中で説明されています。Ptkdbは使いやすいので、初級および中級のPerlプログラマーにとって必需品です。Perlのエキスパートにとってさえ便利なものとなり得ます (こうした最新式のグラフィカル・インターフェースを使っていることを人に教えなければの話ですが)。

独自のPerlシェルの作成

デバッガーを使用するのが大げさな場合もあります。たとえば、大きなプログラムの中の単純な部分を、残りの部分から分離して テストしたい場合、デバッガーを使うのは、そのタスクにとって複雑すぎることかもしれません。こうした場面ではPerlシェルが役に立ちます。

確かに、Perlシェルへの有効なアプローチはほかにも存在しますが、ここでは、大部分の日常業務に非常に役立つ (事実、わたしはいつもこれを使用しています)、1つの汎用ソリューションを検討してみます。ツールを理解できたなら、ご自分の必要と好みに合わせて、これを自由に調整してください。

以下のコードには、Term::ReadLineモジュールが必要です。これは、Devel::ptkdbの場合とほぼ同様な方法で、CPANからダウンロードして、インストールできます。

Perlシェル
#!/usr/bin/perl -w
use Term::ReadLine;
use Data::Dumper;
my $historyfile = $ENV{HOME} . '/.phistory';
my $term = new Term::ReadLine 'Perl Shell';
sub save_list
{ my $f = shift; my $l = shift; open F, $f; print F "$_\n" foreach @$l
}
if (open H, $historyfile)
{
 @h =<h>;
 chomp @h;
 close H;
 $h{$_} = 1 foreach @h;
 $term->addhistory($_) foreach keys %h;
}
while ( defined ($_ = $term->readline("My Perl Shell> ")) )
{
  my $res = eval($_);
  warn $@ if $@;
  unless ($@)
  {
   open H, ">$historyfile";
   print H "$_\n";
   close H;
   print "\n", Data::Dumper->Dump([$res], ['Result']);
  }
  $term->addhistory($_) if /\S/;
}

このPerlシェルは、いくつかの事柄を上手に行い、またある事柄をまずまず上手にこなします。

第一に、このシェルは、既に入力されたコマンドの固有のヒストリーを、ユーザーのホーム・ディレクトリーの ".phistory" というファイルに保持します。あるコマンドを2回入力した場合、1つのコピーだけが残ります ($historyfileを開き、ヒストリー行をそこから読む関数を参照してください)。

新しいコマンドを入力するたびに、コマンドのリストが .phistoryファイルに保管されます。したがって、入力したコマンドがシェルを破損させた場合でも、最後のセッションのヒストリーが失われることはありません。

Term::ReadLineモジュールにより、実行するためにコマンドを入力するのが容易になります。コマンドは一度に1行だけに限定されているので、懐かしいbuggy.plを次のように書くことが可能です。

Perlシェル用のbuggy.pl
Da Perl Shell> use strict
$Result = undef;
Perl Shell> print "$_: " .<> foreach (0..20)
0: ...
1: ...

問題は、当然、<> 入力演算子が結局はシェル自身の入力を取得するに過ぎないということです。したがって、Perlシェルでは<> またはSTDINを使用しないでください。そうするなら、事がより困難になるからです。代わりに、次の例を試してみてください。

Perlシェル用のbuggy.pl (バグ修正版)
Perl Shell> open F, "buggy.pl"
$Result = 1;
Perl Shell> foreach (0..20) { last if eof(F); print "$_: " .<f>; }
0: #!/usr/bin/perl -w
1:
2: use strict;
3:
4: foreach (0..20)
5: {
6:  my $line =<>;
7:  last unless defined $line;          # exit loop if $line is not defined
8:  print "$_ : $line";
9: }
$Result = undef;

お気付きのとおり、シェルが役に立つのは、ステートメントを1行にまとめるのが容易な場合です。シェルは、分離したバグ用のソリューションとして驚くほど一般的なものでもあり、学習のためのすばらしい環境を提供してくれます。少し練習したら、デバッグ用のPerlシェルを独力で作成できるかどうかということと、自分の学習の程度を調べてみてください!

ツールの兵器庫を作り上げる

この記事では、組み込み (標準装備の) Perlデバッガー、Devel::ptkdb、および関連ツールのごく基本的な点を考慮したに過ぎません。Perlのデバッグには、もっとたくさんの方法があります。重要なのは、デバッグのプロセスを理解することです。すなわち、どのようにバグに気付き、解決し、修正するかということです。もちろん、単一の点として最も重要なのは、プログラムの要件に関する包括的な理解を得るようにするということです。

Perlの組み込み (標準装備の) デバッガーは非常に強力ですが、初級または中級のPerlプログラマーには不向きです。(例外はEmacsを使った場合です。この場合、組み込みデバッガーは、初級者にとっても便利なものとなり得ます。ただし、Emacsの下でのデバッグについての知識が必要です。)

Devel::ptkdbモジュールとデバッガーは、(その能力と使いやすさを考えると) 初級および中級のプログラマーにとって、断然に優れた選択肢です。一方、Perlシェルは個別設定可能なデバッグ・ソリューションであり、コードの小さな一部分に含まれる、分離された問題に適しています。

ソフトウェア・テスターはみな、GUDモードのEmacsであれ、Perlシェルであれ、あるいはコードの至る所でprintステートメントを使うことであれ、自分自身のデバッグ・ツールの兵器庫を作り上げるものです。願わくば、ここで調べたツールによって、読者のデバッグが少しでも容易になりますように。


ダウンロード可能なリソース


関連トピック

  • 何か必要なPerlモジュールがある場合は、CPAN にアクセスしてください。
  • Perl.com でPerl情報と関連リソースを調べてください。
  • Perlの 'perldebug' perldocページを表示するには、コマンド・プロンプトで "perldoc perldebug" と入力し、これを検索してください (ただし、Perl全体が自分のパスにインストールされていることが必要)。
  • Andrew E. Page作のDevel::ptkdbモジュールをダウンロードしてください。
  • Cigital.com かFAQS.org でcomp.software.testing FAQを見つけてください。
  • comp.software.testing FAQのソフトウェア・テストおよびQAのリソースが必要な場合は、以下のサイトにアクセスしてください。
  • Programming Perl Third Edition (Larry Wall、Tom Christiansen、Jon Orwant共著; O'Reilly & Associates, 2000) は、現時点で最良のPerlの手引き書であり、5.005と5.6.0に沿った最新の情報が掲載されています。第20章では、標準Perlデバッガーについて扱われています。
  • Perl Cookbook (Tom Christiansen、Nathan Torkington共著; O'Reilly & Associates, 1998) は、Perlのすべての問題を扱った実用書として最も信頼の置けるものです。5.6.0の記述がなく、いくらか時代遅れの部分はありますが、大変値打ちがあることに変わりありません。
  • The Mythical Man-Month, 20th Anniversary Edition (Fred P. Brooks著; Addison-Wesley, 1995) は、特にプロジェクト管理者とコーダーにとってすばらしい万能の読み物です。デバッグについても、よく説明されています。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=229864
ArticleTitle=洗練されたPerl: 楽々Perlデバッグ
publish-date=11012000