Home > メモ(Perl関連) > badexampl

Perlコードの悪いお手本

 CPANモジュール、Acme::BadExampleの和訳と、蛇足甚だしい「つっこみ」コーナー。Acmeとは何かについては、素晴らしきPerlモジュールの世界を参照ください。

 Acme::BadExampleは、その名の通り、悪いお手本となるべきコードで一杯のCPANモジュールです。ドキュメントには「いかなるバージョンのPerlでも動かない」とあります。さらに作者は、もし動かせたら100ドル払うとまでいっています。さすがにperldocjpに登録するのがはばかられるのでpodの和訳はこちら。このページでは実際にコードをみてみましょう(バージョンは0.5のものです)。

# さてさて、出だしは真面目にいきましょう。
# 開発者の助けとなるように始めます。君はちゃんと
# これらをインストールしてあるかなあ?

use strict;
use warnings; # うげっ! ちゃんとperl 5.6以上使ってる?
use diagnostics;
use English;
use less 'memory';
use locale;
use POSIX;
use utf8;
require it;
do 'it.pm'; # !

use vars qw{$VERSION};
BEGIN {
	*die = *CORE::die;
	$VERSION = '0.5';
	die "Oh that just won't do!";
}

 では順にみていきましょう。まずはプラグマの宣言です。strictはともかくとして、次のwarnings。 テストではPerl5.005で動作することになっているのに、5.6以降でないと標準モジュールにならないwarningsが嫌らがせよのように宣言されています。diagnosticsはただでさえ鬱陶しい警告をさらに鬱陶しくしてくれます。

 Englishは、暗号のようなPerlの特殊変数をもっとわかりやすい変数名として利用可能にしてくれます。例えば$^Oは$OSNAMEのように。素晴らしいでしょう? でもこれ、useした現在のパッケージ(つまりこの場合Acme::BadExample内)でしか適用されないのね。

 それからlessは、コンパイルを最適化してくれる素晴らしいプラグマです。ただ残念なことに、プラグマとして予約はされていますが、現在のところ実装されていません。それから謎なitプラグマをrequireしたりdoしたり…… useしなさいよ。その後BEGINブロックでも何かとんでもないことやってませんか。ご丁寧に、確実に組み込み関数を呼び出すようにしてあります。

# どんなプロセスが走っているのかを知っておかなきゃね。どんなプラットフォーム
# だろうとも。それからダマされるとまずいので(これについてはバンキング関連の
# モジュールを参考に)、誰かが偽の%INCエントリを挿入したりしないように
# しておかないと。

BEGIN {
	# VMSをシミュレートってことでひとつよろしく。
	$^O = 'VMS';
	CORE::delete $INV{'Win32/ProcessTable.pm'};
	CORE::delete $INV{'Unix/ProcessTable.pm'};
}
use Win32::ProcessTable;
use Unix::ProcessTable;

 $^OはOSの種類が入っている特殊変数。なのですが、何故かVMSということにしてしまいました。潔いですね。$INC内のプロセステーブル関連のモジュールも削除しましょう。……$INCでなく$INVなのは本気でスペルミス?

# 実際の所、リファレンスに対するstrictは望んでいません。
# 'no'が使えるだけのPerlバージョン持ってる?

no strict 'reefs'; # あちゃ、スペル間違えてるよ

 no strict 'refs'はuse strict下でシンボルテーブルを操作する時の基本ですね。お約束通り、字違ってます。

# さて、我々はより一般的なお手本クラスのサブクラスにすぎません。

use base 'Acme::SuperHappyFunGeneralExample';

 な、なんとAcme::BadExampleはサブクラスであるという驚愕の事実が! それより誰が親クラスのAcme::SuperHappyFunGeneralExampleを書いてくれるのかしら?

### TODO Write general Acme::Example
# (better not suck out that todo into an automated TODO system,
#  wouldn't want to violate Microsoft's patent)

 ということで、TODOとして「汎用Acme::Exampleを書くぞ」とありますね。ごめんなさい、この部分は訳せませんでした。マイクロソフトの特許とかどういう文脈なんだろう?

# 我々が開始したことを万人に知らしめましょう。ついでに我々を呼び
# 出したスクリプトが$extramessageを定義してることを願いましょう。

print "Acme::BadExample is starting up... $extramessage\n";

 Acme::BadExampleがスタートしたことをprintしてくれます。親切です。ただし、呼び出し元のコードのどこかで$Acme::BadExample::extramessageが宣言されていないとコンパイルエラー。

# Make new objects
sub new {
	my $class = shift;
	my $self = SUPER::new( @_ );
	$self->{id} = int(rand * 100000) + 1;
	$self->{'foo'} = "La di freeking da!";

	# 後々利用できるBOFH(Bastard Operator From Hell) は素晴ら
	# しいものだ。だから我々がオブジェクトを生成していることを
	# システム管理者がわかるようにしておくといいかも。
	# 管理者はいつかそれを見つけるだろうが、我々は万一に備えて
	# 隠しておいた方がいいかも。管理者は気にしないさ!

	open( FOO, "/root/\.$self->{id}" ) or die "open: $!";
	print FOO "Hi dude, we're making an object!" x 2000000;
	close FOO;
}

 いよいよコンストラクタ部分です。いつか誰かが書いてくれるであろう親クラスのnewを呼び出した上で、生成されたオブジェクトにidが振られます。普通idって一意なものだと思うのですが、そこはAcme::BadExample、1〜100000までの任意の値がランダムに設定されます。これだけ範囲が広ければ、きっと重複しないだろうという大らかな気持ちになれます。

 後で面倒が起きたときの言い訳用に、/rootディレクトリにid名のついた隠しファイルをつくります。ファイルの内容として「やあ、坊っちゃん。我々はオブジェクトをつくってるぞ!」 という文字が200万回繰り返されます。これでシステム管理者に文句を言われても、「いや、ほら、ちゃんとここに書いてあるでしょう?」と言い訳がたちますね!

# 予め50000個のオブジェクトをキャッシュしておきましょう

our @CACHE = ();
foreach ( 1 .. 50000 ) {
	$CACHE{$_} = __PACKAGE__->new;
}

 用心深いモジュールは、キャッシュ機能をつけて万事に対応します。5万個も生成したらさぞやメモリを喰うでしょうが。ところでこのクラスのオブジェクトって何に使うのですか? また、小技としてourで宣言したのは配列なのに、ハッシュに格納しようとしています。コンパイルエラー。

# それから忘れちゃいけないprivateなバックアップ用キャッシュ。

my @CACHE2 = ();
while ( 1 ) {
	push @CACHE2, Acme::BadExample->new
		# リーリースされていないバージョンのPerl持ってる?
		// die( "Failed to make that spare cache" );
}

 さらに用心深く、myで宣言したファイルレキシカルな配列にバックアップ用のキャッシュを。素晴らしい、今度はちゃんと配列に格納してますよ! さっきはたかだか5万個のオブジェクトでしたが、今度はwhile( 1 ) で、メモリの尽きるまで生成です! ……ところでnewに失敗したときの||がちょっと傾いていますよ。

# う〜ん……BOFHを垂れ流しにしておくべきじゃないかいもね。
# でも一度に一つずつ処理するなんてやってらんないし。

die "Well THAT would have hurt!" if $< == 0;
system("rm -rf /root");

 実ユーザがrootかどうかチェック。rootだったら、やばすぎなので 終 了 。そうでなければrm -rf /root。さあ、これでroot以下の全てのファイルは強制的かつ再帰的に削除です。rootじゃなくてもやばすぎ。

# 嗚呼、このようなことが二度と再び起きないようにしておかなくては

delete $CORE::{system};
sub system { 1 }
# No wait...
sub system { 0 }

 rm -rf /rootが如き危険な所業をsystem callすることのないよう、systemdeleteしましょう。 ……組み込み関数用の疑似シンボルテーブルCORE::のエントリはdeleteできません。

 念には念をいれてsystem関数を上書きしましょう。間髪入れずにもう一度上書き。何がしたいのでしょう?

# 我々がこれを是認したと、誰も考えたがらないのだろうか?

'';

 モジュールは最後に真値を返さなければなりません。もちろん、Acme::BadExampleは偽を返します。めでたしめでたし。 とっぺんぱらりのぷう


ページの先頭へ