レベル: 中級 Teodor Zlatanov (tzz@bu.edu), Programmer, Gold Software Systems
2000年 6月 01日 Perl は、比較的簡単な言語です。Perl は、単純な仕事を簡単にし、難しい仕事を可能にすることを目的に設計されています。しかし、この言語に組み込まれている単純さがワナになる場合があります。プログラマーは、本質的に、プログラムのアーキテクチャーを文書化したり設計したりすることを嫌います。純粋なコードを書くことの醍醐味は、直接マシンに触れ、何をすべきかをマシンに正確に伝えることにあります。本稿では、コードを分かりやすくすることによって Perl プログラムの信頼性と保守容易性を向上させる技法を説明します。ここで説明するヒントは初級から中級の Perl プログラマーを対象としており、コーディング・スタイルを変えることが目的ではなく、良い基準を確立することにむしろ主眼を置いています。
これまでの考え方 (またはコーディングの仕方) を大幅に変更しなくても、Perl で分かりやすさを追究することは可能です。Perl で複雑なタスクを書くのは難しいことではありますが、不可能ではありません。手際良く行うことだってできるのです。本稿で述べられていることを念頭に置くことによって、プログラムを書いた本人以外のプログラマーでも、プログラムを理解して保守することが可能になるでしょう。以下に挙げる 9 つのヒントを活用すれば、引き続き Perl を使い、自分のスタイルを保持しながらも、分かりやすくて安定したプログラムを作成できるようになるでしょう。
「改善の余地がない」
「自分には、C/C++/Ada/アセンブラー/Pascal/LISP/Java の長年の経験がある。自分のコードは完璧だ。改善の話などしないで欲しい。」とお思いではありませんか。しかし、私はこう思います。「プログラミングに完璧などありえない、あるのは完全性の追求である」と。優秀なプログラマーというのは、日々新しいことを学び、自分の技法を絶えず向上させる努力をするものだと思います。
言語としての Perl には、大きな柔軟性があります。たとえば、次のようにして自分の環境を出力することができます (バージョン 5.005 以降)。
1 行で、%ENV の内容を出力する
print "$_ => $ENV{$_}\n" foreach(sort keys %ENV);
|
同じことを次のようにして行うこともできます。
行を分割して、%ENV の内容を出力する
foreach (sort keys %ENV)
{
print "$_ => $ENV{$_}\n";
}
|
あるいは、Data::Dumper モジュールを使用することもできます。
Data::Dumper を使って、%ENV の内容を出力する
use Data::Dumper;
print Dumper(\%ENV);
|
 |
Perl のディストリビューションに付属する文書
Perl の文書 ("xyz" ページなど) は、Perl のディストリビューションに付属しています。こうしたページを表示するには、コマンド行で "perldocxyz" と入力します。これらの文書は、HTML 文書で入手することができます。
|
|
上記の方法はすべて、デバッグのコンテキストにおいて同じことを行います。最も理解しやすくて、文書化と保守が簡単なのはどれでしょうか。当然、3 番目の方法です。Data::Dumper を使用したことがない方には、資料を読んで ("perldoc Data::Dumper")、プログラムの中で使ってみることをお勧めします。
速度は、重要な要素ではありますが、プログラムのコードの改善の度合いを測る唯一の尺度ではありません。テスト、文書化、保守がそれぞれ容易かどうかという点にも留意すべきです。これはあらゆるソフトウェアのプロジェクトにあてはまります。Perl のような柔軟な言語の場合、ソフトウェア・プロジェクトのすべての段階で良好なコーディングを簡単に行うことができますが、コーディング前の段階 (要件の収集とアーキテクチャーの設計) だけは例外です。
コメントを書く: 分かりやすいコメントを書く習慣をつける
十分な文書化に勝るものはありません。よく分かるということは、分かりきっていることを繰り返すことを意味する場合があります。コードを、世界に向けて提示するものだと考えてみてください。世界中にはたくさんの人がいます。冗長だと思えるコメントでも、それを 1 つ追加して書くことで、誰かが楽をできるかもしれないのです。もしかすると、楽をできるのは、あなた自身かもしれません。たとえば、5 年たってから、新しい機能を追加しようと思うような場合です。
プログラムを書くときには、設計が鍵を握ります。あらゆる詳細な点まで事前に決めておく必要はありませんが、プログラムをいくつかのコンポーネント・パーツに分割し、ギャップを埋めるためにコメントを使用する必要はあります。
小さな例を 1 つ取り上げてみましょう。/etc/hosts ファイルにある名前を除いて、すべての入力を反転するプログラムです。
入力反転プログラム (最初のバージョン)
#!/usr/bin/perl -w
# author, date, revision, etc.
# brief summary of program
# modules used summary
# pragmas used summary
# function A summary
# function B summary
# main loop summary
# POD documentation
|
次に、各部分について詳しく説明します。
入力反転プログラム (2 番目のバージョン)
#!/usr/bin/perl -w
# author, date, revision, etc.
# This program will process user input with two functions.
# Command-line arguments will be treated as input file sources.
# No flags are allowed.
# modules used: none
# pragmas used: strict
use strict;
# function A: return a parameter with the letters reversed
# function B: return true if a word is in the /etc/hosts file
# main loop: go through input lines, passing each word to B
# and, if B returns false, to A
__END__
POD documentation
|
そして、最終バージョンでは、コードとコメントの形になります。スクリプト reverser.pl を参照してください。
意味のあるループを書く
ループは、1 行のループと複数行のループに分類できます。何でも 1 行のループに詰め込もうとするのはやめてください。たとえそれが自然に見えてもです。例を挙げてみましょう。
すべての環境変数を小文字で (語頭は大文字) 出力してソートする、1 行のループ
print ucfirst lc $_, "\n" foreach (sort keys %ENV);
|
これを次の例と比較してみてください。
すべての環境変数を小文字で (語頭は大文字) 出力してソートする、複数行のループ
foreach (sort keys %ENV)
{
print ucfirst lc $_, "\n";
}
|
2 番目のループは、最初のループとまったく同じジョブを行います。あなたがコードを保守する立場にいるとしたら、どちらが良いと思いますか。そうです。複数行のループを読む方がずっと容易です。
C ベースの言語に関する悪い事柄は忘れるようにしてください。これは、一般に、良いアドバイスと言えます。しかし、特にループに関しては、条件全体を否定する必要はありません。また、'for' ステートメントの使用は最小限にとどめるべきです。
if (not 条件) を unless (条件) に変換してください。
if を unless にする
print "Yes" if not $false;
print "Yes" if !$false;
# becomes...
print "Yes" unless $false;
|
同様に、while (not 条件) は until (条件) に相当します。
リスト 9: while を until にする
print "No signal" while !$signal;
print "No signal" while not $signal;
# becomes...
print "No signal" until $signal;
|
'for' ループを使用するのは賢明ではありません。'for' ループは、1 行に 3 つの事柄 (初期条件、増分、および終了条件) を詰め込みます。Perl が登場するまでは 'for' ループを使わざるをえませんでしたが、現在では 'foreach' ループがあるので、'for' ループを使用する必要はなくなりました。
たとえば、変数 $i に 1 ? 10 を入れてみましょう。
foreach を使った繰り返し
foreach $i (1 .. 10)
{
# do something
}
|
これを、'for' ループを使って同じことをする場合と比較してください。
for を使った繰り返し
for ($i=1; $i<= 10; $i++)
{
# do something
}
|
'for' ループが便利な場合もありますが、このような場合には、十分な文書化を行い、'foreach' が不適切だった理由を示すようにしましょう。初級から中級の Perl プログラマーの場合、'for' 構成を使用するのは益よりも害が大きくなりがちです。
分かりやすさへの道
Perl には、C の lint や -Wall がありませんが、以下を考慮してみましょう。
スクリプトを実行するときには、-w フラグを使用するようにします。Perl インタープリターに対して 'use strict' 命令を使用すると、プログラムは、読みやすく、構造化が図られた、実行しやすいものとなります。これは、良いプログラマーになるための魔法というわけではありませんが、コードを大幅に改善することはできるはずです。たとえば、デフォルトでは、変数とハッシュ・キーは、最初に使用する時には作成されません。変数は宣言しなければなりません。そうすれば、多くの一般的なバグを避けることができるでしょう。
use strict と -w の使用法の例については、reverser.pl を参照してください。
定数を変数または関数として定義しないようにしてください。'use constant' ディレクティブを使用する方が賢明です。'use constant' ディレクティブをサポートしていない Perl をご使用の場合は、Perl をアップグレードしてください。
use constant の例
use constant TIMESLICE => 15;
|
関数は、プロトタイプを使用するようにします。行き当たりばったりで、関数の使用を決めないようにしましょう。コーディングする前に設計することによって、作業がもっと容易になることを常に念頭に置いておくことが大切です。
スカラー、数値、およびストリングの相違を意識するようにしてください。FAQ や、「Programming Perl」または「Learning Perl」のなどの資料を参照してください。この点についての学習は、読者の方々への宿題にしたいと思います。Perl では、数値とストリング相互間の変換は容易ですが、見つけるのに数時間または数日を要する、小さなバグが生じることがあります。
分かりやすさに関するトピックには、限りがありません。分かりやすいコードを作成できるよう、常に学びつつ努力を続けていかなければなりません。Perl の構文において王道というものは存在しないのです。perlsyn および perlstyle の各ページもご覧ください。このトピックに関連する点が取り扱われています。
map と grep をマスターする
Perl をさらに機能的な言語にしたいと考えるプログラマーにとって、map と grep はその渇望を満たすものとなります。map と grep は、リストの要素ごとに、ブロックまたは式の評価を行います。これらは、ブロック構文において大変役に立ちます。この構文に関する、より進んだ例を、簡単に見てみましょう。map と grep は、$_ を、ブロックの中で現在検査している要素に設定します。
しばしば引き合いに出される Schwartzian Transform は、フィールドの 1 つによる一時配列にほかなりません。例を示します。
Schwartzian Transform
@sorted_list = map { $_->[0] }
sort { $a->[1]<=> $b->[1] } # note that this is a numeric sort
map { [$_, index_function($_)] }
@unsorted_list;
|
ソートされていないリストには、何でも好きなデータを入れることができます。index_function を作成してください。これは、ソートされていないリストの各要素をソートするためのキーを生成する関数です。変換を実行すると、要素がキーに従って数値順にソートされます。
このコード・リストが理解できない場合は、map 関数と sort 関数に関する説明を注意深く読んでください。無名の配列参照と配列要素の間接参照の、Perl における表記法を理解しておく必要があります。「Effective Perl Programming」サイト (「参考文献」参照) には、Schwartzian Transform に関する詳細な説明があります。
map と grep を使うと、手際の良い技法を数多く利用できます。例を挙げます。
配列を大文字にマッピングする
@uc = map { uc } @values;
|
上記の例では、配列内の各要素に uc 関数をマッピングすることにより、配列を大文字に変換します。どこかで以前に見かけませんでしたか。確かに見ました。環境変数を出力するためのループにこの手法を当てはめると、読みやすさを改善できるかもしれません。
すべての環境変数を小文字で (語頭は大文字) 出力してソートする、1 行のループ (マップを使用)
print $_, "\n" foreach (map {ucfirst lc } sort keys %ENV);
|
次に挙げる方がより良いコードです。というのは、より分かりやすく、かつ書式と関数が分けられているからです。
すべての環境変数を小文字で (語頭は大文字) 出力してソートする、複数行のループ (マップを使用)
foreach (map {ucfirst lc } sort keys %ENV)
{
print $_, "\n"
}
|
grep は、式またはブロックが真を戻す場合だけ要素を "パススルーする" という点を除けば、map とまったく同様に機能します。map の方は、何でもパススルーします。たとえば、配列の中から有効なファイルである要素だけを抽出する場合は、grep を使用できます。
妥当性を調べるためにファイル名の配列をフィルターに掛ける
@valid = grep { -f } @names;
|
-f 演算子は、ファイルが存在する場合にだけ真を戻します。
モジュールを使用するタイミングを見極める
ある特定の機能を持ったプログラムが複数あることに気付いたその時こそ、モジュール化を考える良いタイミングだと言えます。モジュールの中でオブジェクト指向を使用する必要はありません。簡単に説明しましょう。
Exporter モジュールの使用
package My::Package;
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(my_function); # symbols to export by default
sub my_function()
{
}
1;
|
モジュール My::Package を作成して、そこから関数 my_function にエクスポートするには、これで十分です。これで、他のプログラムに "use My::Package" が出てくる場合に、自動的に関数 "my_function" がインポートされるようになります。
モジュールについては、言うべきことが他にもたくさんあります。perlmod 文書を参照するとともに、この記事の「参考文献」のセクションをご覧ください。モジュールについて学習し、モジュールを使用する習慣をつけるようにしてください。モジュールを使うことによって、プログラミングがもっと楽になるはずです。一般的な指針として、ある関数を複数のプログラムで使う場合には、それをモジュールにするべきです。
オブジェクト指向プログラミングと他の見苦しい習慣
オブジェクト指向プログラミング (OOP) はすばらしいツールです。しかし、だからと言ってむやみに信奉しないようにしてください。OOP には次のことができません。
- 世の中の諸問題を解決すること
- プロジェクトの途中で開発のスケジュールを短縮すること
- 高価なツールを必要とすること
- 設計を不要にすること (「オブジェクトは自分自身の面倒を見る」から)
一方、OOP には次のことができます。
- 正しく使用すれば、プログラミングを楽にすること
- 抽象概念をす早く具体化すること
- Perl のモジュールと良好なインターフェースを図ること
手短に言うと、OOP の使用は奨励しますが、これで何もかも行おうとはしないでください、ということです。Perl における OOP の概要については、「参考文献」と perltoot ページをご覧ください。OOP はデータ抽象化と同じくらい単純になり得る一方、会社全体に渡る方法論と同じくらい複雑にもなり得ます。
PSI::ESP モジュール (
ESP.pm) に、単純なオブジェクトの一例を示します。
ptkdb と他の一連の子音
できるだけ早く CPAN (「参考文献」参照) から ptkdb デバッガーを入手してください。これにはいくつかのモジュールが必要ですが、十分にその価値があります。デバッガーは、プログラム内、およびロード・モジュール内のデータとコードを表示します。私自身、ptkdb のおかげで、ストレスをためたり、髪をかきむしったりせずに済んだことが何度もありました。
PSI::ESP モジュール
Perl の世界では、PSI::ESP モジュールは有名です。しかし、残念ながら、その真の知恵に至ることのできる人はほんのわずかです。それ以外の残りの多くのプログラマーは、お互いに助け合っていかなければなりません。Perl の問題、または独自の Perl のスタイルに関して助けが必要な場合は、SOS を出す際に、状況の十分な説明、すでに試みた事柄、それからコード全体を含めることを忘れないようにしてください。
ここに、ESP モジュールの予備的なバージョンを示します。このモジュールは、遭遇し得る Perl のすべての問題を解決できうるかもしれません。お役に立てば幸いです。
このモジュール (
ESP.pm) を現行ディレクトリーの PSI サブディレクトリーに入れたら、次の構文でこれを使用できます。
perl -I. -MPSI::ESP -e 'use PSI::ESP; $p = new PSI::ESP; print $p->reason'
結論
この記事が、読者の皆様の Perl のスキルの向上に多少なりとも貢献できることを願ってやみません。ここに示した情報は、「参考文献」に挙げる文献で補完することができます。それでは、Perl 追究の旅をお楽しみください。Perl が、読者の皆様にとって、常に洞察と興奮の源となっていただければ幸いです。
参考文献
- Effective Perl Programming (Joseph Hall、Randal Schwartz 共著、Addison Wesley、1998) は、書籍の形で入手できる、Perl のヒントと秘けつの信頼の置ける情報源です。特定のタスクというよりも、言語中心の説明です。
- Object Oriented Perl (Damian Conway 著、Manning Publications、2000) は、モジュールとオブジェクト指向に関する優れたガイドです。
- Programming Perl, 2nd Edition (Larry Wall、Tom Christiansen、Randal L. Schwartz 共著、O'Reilly、1996) は、現時点で最も優れた Perl のガイドですが、5.005 と 5.6.0 の説明が欠けていて、現在では若干古くなっています。
- USENET の comp.lang.perl.misc ニュースグループは、学習のための優れた場となっています。投稿する場合は、そのしばらくは記事をご覧になり、FAQ をチェックするようにし、c.l.p.misc コミュニティーに参加するにあたって上手に振る舞うようにしてください。特に、セクション 9 をご覧ください。
著者について  | 
|  | Teodor Zlatanov 氏は、1999 年にボストン大学を卒業し、コンピューター・エンジニアリングの分野で理学修士号を取得しました。1992 年以来、Perl、Java、C、および C++ を使用して、プログラマーとして働いています。興味の対象は、オープン・ソースのテキスト分析処理、3 層クライアント・サーバー・データベース・アーキテクチャー、UNIX システム管理、CORBA、およびプロジェクト管理です。メール・アドレスは
tzz@bu.edu です。 |
記事の評価
|