2010年12月31日金曜日

転職活動中です。



WEB屋さんに戻る(?)予定


某企業さんの1次面接に受かりました。嬉しい。
転職活動1社目なんですが、ここに入りたいなぁと思ってます。


勤務中の会社


社員数5桁の超まったり企業。
皮算用ですが、もし会社を定年まで勤め上げ、かつ会社の業績も今のままだとすると、退職金は2000万円以上は確実に貰えた。


なぜ辞めるの


最終製品に興味がなさすぎた。
製品・サービスの工程すべてに携わりたい。
開発スピードが遅い。
WEBが好き。自分は人が好きで、WEBは人が交わる大舞台だから。


安定生活を捨てるに至った思考


何日延命したいの?

1日8時間の時間を切り売りした分で、自分は何日分の将来を買えるのか。
今の8時間を充実したものにする方がよくないか。


いつか死ぬ

将来のことを考えると人間は保守的になりますが、「いつか死ぬ」という基本的なことを意識し始めると、
今この瞬間がとても大事だと思えてきて、未来のほうが霞み始めてきました。
いつかはすぐそこにあるかもしれない。


定年なんてヤダよ


いつまでだって面白い事、面白い人と絡んでいたい。





2010年12月25日土曜日

AnyEventでイベント駆動プログラミング



参考


AnyEventとPlackの社内勉強会資料
Perl 非同期プログラミング


イベント駆動とは


割り込み処理。


AnyEventとは


イベント駆動プログラム用モジュール。他のイベント駆動モジュールのラッパーとしても動作するので、これ1つで良いとか。まぁ、なんか争ってるみたいですが。


割り込み処理なので、マルチプロセスじゃないです。


AnyEvent vs AE


牧さんのスライドではスタイルが違うだけとあるけど、どっかのblogではAEのほうが速いとあった。
AEは短く書けるけど引数の順番覚えないといけないから、引数がハッシュのAnyEventを使うよ。


書いてみた



#instant_noodle.pl
use strict;
use AnyEvent::Util qw(fh_nonblocking);

fh_nonblocking( \*STDIN, 1 );


my $CPS_for_noodle = 0;

sub input_time_from_STDIN {
my $cv = AnyEvent->condvar;
my $w;

print "自然数を入力してください\n";
$w = AnyEvent->io(
fh => \*STDIN,
poll => 'r',
cb => sub {
undef $w;
my $line = <STDIN>;
$cv->send($line);
}
);
return $cv;
}

sub count_timer {
my $cv = AnyEvent->condvar;
my $w;
$w = AnyEvent->timer(
after => $CPS_for_noodle,
cb => sub {
print $CPS_for_noodle / 60, "分経ちました!\n";
undef $w;
$cv->send;

}
);
return $cv;
}


my $main_cv = AnyEvent->condvar;
{
my $cv = input_time_from_STDIN();
$cv->cb(
sub {
($CPS_for_noodle) = $_[0]->recv;
print "set ", $CPS_for_noodle / 60, " minutes\n\n";
print "出来たら呼ぶから、とりあえず机の上を片付けろ!\n";
$main_cv->send;
}
);

}
$main_cv->recv;

$main_cv = AnyEvent->condvar;
{
my $cv = count_timer($CPS_for_noodle);
$cv->cb(
sub {
print "\a";
print "ラーメンできたよ!\n";
$main_cv->send;
}
);

}
$main_cv->recv;



ださい



#毎回、このスタイルで書くのか・・・・
$main_cv = AnyEvent->condvar;
{
・・・・・・
}
$main_cv->recv;



とりあえず、他の人のソースをちゃんと読まないうちにゴリ押しで書くと
大変なことになりそうだと思いました。





2010年12月20日月曜日

さくらインターネットVPSを申し込んだよ



メモメモ。


はじめに


さくらのVPSのデフォルトでは、多くのデーモンを停止済みにしているほか、selinuxもdisabledとなっています。


VSPコントロールパネル


まず登録後に送られてくるアドレスから「VPSコントロールパネル」にいく。
「仮想サーバ操作」の起動をクリック。


次にマニュアルをクリックして、ちょっとお勉強にいく。
さくらのVPS OSセットアップ情報
ざっと眺める。


SELinuxってよく聞くけどなんだっけ?


セキュアOS機能



SELinuxは,各プロセスがアクセスできるリソース(この場合のリソースとは,個々のファイルやディレクトリ全体など)を制限し,本当に必要なリソースにだけアクセスできるようにする。例えば,Webサーバーの場合は,Webページにだけアクセスできるようにする。Sambaサーバーなら,Sambaの共有ファイルにだけアクセスできるようにするわけだ。そして,関係ないファイルへのアクセスは一切拒否するようにしておく。
(略)
プロセスのリソースへのアクセス制限は,OSレベルで強制されており,抜け道は無い。たとえroot権限で動作するプロセスであっても強制されるので,攻撃者にroot権限を奪取されても操作可能な範囲は限定される




パスワードログイン



$ ssh root@xx.xxx.xxx.xx
The authenticity of host 'xx.xxx.xxx.xx (xx.xxx.xxx.xx)' can't be established.<--- ~/.ssh/known_hosts に登録されていない
RSA key fingerprint is xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
Are you sure you want to continue connecting (yes/no)? yes <--- 登録
Warning: Permanently added 'xx.xxx.xxx.xx' (RSA) to the list of known hosts.
root@xx.xxx.xxx.xx's password:



サーバにemacsをインストール



$sudo yum -y install emacs



デフォルトPortから変更


デフォルトのPort22は攻撃対象になり易いので変更。
サーバの/etc/ssh/sshd_configを編集



#Port 22
Port 10022

/etc/init.d/sshd restart


再起動後に有効。


公開鍵と秘密鍵の作成


パスワードログインは突破されやすいので、鍵を作成した後、パスワードログインを無効にする。


その前に公開鍵と秘密鍵の説明

・公開鍵 => Aさんがみんなにばら撒いた鍵
・秘密鍵 => Aさんしか作り方を知らない「宝箱」


Bさんの所に、Aさん(かも知れない人物)から宝箱が届いた時に、
Aさんが公開している鍵で開けることができたのなら、それは確かにAさんからきた宝箱だと証明されたことになる。


というわけで

先ほどのBさんがサーバで、Aさんがローカルです。
予め、ローカルで作成した公開鍵をサーバに渡しておいて、ローカルの秘密鍵(宝箱)でサーバにアクセスする。
これで、私はサーバさんが知っているユーザですよ、という証明となる。


鍵を作成

次のサイトをみて参考に設定。
CentOSをサーバーとして活用するための基本的な設定
ServersMan@VPS :: CentOS5.5 の設定
さくらVPSのsshによる鍵認証の設定
さくらの VPS 契約から作業環境を作るまでの覚書
404 Not Found



DSA方式で作成。
% ssh-keygen -b 1024 -t dsa
//ここでパスフレーズの入力を求められる。後で使用するので忘れないように。
% cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys
% chmod 600 ~/.ssh/authorized_keys



作成した公開鍵authorized_keysをサーバにftpで送る


ftpの平文通信はまずいので他のを使う。
2つ選択肢があるが、今回はFTPSを使用してみる。
参考サイト
FTP, FTPS, SFTP
SFTPとFTPSの違い



  • SFTP

    • SSHで暗号化



  • FTPS

    • SSL/TSLで暗号化





ただし、その前にSElinuxを無効にしておく。


SElinuxを無効化


/etc/sysconfig/selinuxのSELINUX=enforceをSELINUX=disabledに。(と思ったらはじめから無効でした)



サーバにvsftpdのインストール

参考サイト
WordPress on CentOS vsftpの設定
FTPサーバ構築(vsftpd)- CentOSで自宅サーバ構築
vsftpによるFTPサーバの構築



$yum -y install vsftpd
/etc/vsftpd/vsftpd.confを編集
anonymous_enable=NO 匿名ユーザのログイン禁止
xferlog_enable=YES   
xferlog_std_format=NO
ascii_upload_enable=YES
ascii_download_enable=YES
use_localtime=YES
ssl_enable=YES
rsa_cert_file=/etc/pki/tls/certs/vsftpd.pem サーバー証明書


本当はもっと設定するけど、とりあえずこれだけ。


vsftpdを起動。


$ service vsftpd start


以下のメッセージがでた・・・。



sftpd for vsftpd: 500 OOPS: SSL: cannot load RSA certificate


設定ファイルに記述した/etc/pki/tls/certs/vsftpd.pemがないか、pemはあるが、設定ファイルで指定していないかどちらか。
vsftpd.pemを作成



$cd /etc/pki/tls/certs/
$make vsftpd.pem


質問内容は単純に答える。


ファイルを転送する

sftpコマンドを使ったファイルの転送
ローカルにて、



$sftp -oPort=***** root@xx.xxx.xx.xxx
sftp> put authorized_keys .ssh<ENTER> サーバで.sshディレクトリ作成済みのこと



さぁ、sshでログイン。



Agent admitted failure to sign using the key.


ぐは、すいません。



$ssh-add ~/.ssh/id_dsa
Enter passphrase for /home/**/.ssh/id_dsa: #鍵を作成した時に入力したフレーズ

$ssh root@59.106.183.58 -p 10022


sshでのログインOK


パスワードログインを禁止



/etc/ssh/sshd_configを編集
PermitRootLogin without-password

sshd再起動
$service sshd restart



iptablesを設定


/etc/sysconfig/iptablesを編集
さくらのVPS を使いはじめる 3 – iptables を設定する
CentOSをサーバーとして活用するための基本的な設定
参考に。
自分のIPは、ルータにアクセスして調べる


設定ファイル注意。起動するとエラー。

/etc/rc.d/init.d/iptables restartでiptables-restore v1.3.5: no command specifiedが出た。
設定ファイルの最終行にCOMMITを記述しているが、ここに改行が入っていなかったせいだった。


めんどくさい


スクリプトを書きましょう。
さくらインターネットVPSの初期設定をまとめてみた


以上


わからないことだらけだけど、やっぱり面白いなぁ。
IT業界で生きていけないかな。





2010年12月18日土曜日

オブジェクト指向を学んだ本たち



まず最初にみたのが、まさかのハッカーと画家





ここで、よくわからないながらも、
・オブジェクト指向は、決まった定義がなくて、いくつかの要素を満たすものだと知る
・オブジェクト指向は魔法じゃない
ということを学ぶ


次に、名前も覚えていない本たちを読むもさっぱりわからず。
・それぞれのコードがほかのコードを汚染しにくいので、多人数での開発に良い
・継承ができる
・物が何をできるかを知っている
などなど・・・


で、記憶に残らないんですよね、読んでも。
なぜそれが必要なのか等のバックグラウンドまで知らないと、上っ面だけ学んでも全然シナプス繋がりません。


そんなときに出会えた最高の本がこちら。
[rakuten:book:13192698:detail]

この中で、松本さんは、犬とか猫とか抽象的な説明してるからわからんのだ、コードはコードでしかねぇ(超意訳)と言っていて、
実際にrubyのコードで説明されていました。
rubyはほとんど触ったことないですが、とてもよくわかりました。


rubyの話になりますが、rubyはほんとに読みやすいですね。
rubyで書かれたgroongaのテストコードもすぐに読めました。


ruby自体が僕の中でメイン言語になることはたぶんないと思いますが、読みやすさなどパクる対象として大いに興味があると思いました。
おしまい。





文と式



JavaScritptではfunctionで始まる文字列は文として認識されて、
文はグローバルとして扱われます。


なので、C言語みたいにプロトタイプ宣言が要らないです。


var test = function (仮引数){ 関数本体 }
は、最初がvarなので、文ではなく、式になります。


似たようなので、最初に混乱するのが、以下。
var test = function (仮引数) {関数本体} (実引数)
と書くと、関数の実行結果が返ってきます。まぁ実引数をとるので、関数が実行されてるんですね。
関数の実行がβ変換にあたるのかなJavaScriptで学ぶ・プログラマのためのラムダ計算


JavaScriptはまだまだわかりませんが、ほぼハッシュと言われているだけあって、読みやすいですね(今のところ)





2010年12月2日木曜日

groongaを使っ・・・えなかった・・・使えた



ドキュメントにしたがって、インストール。
MeCabは先にインストールする。
説明通りに進めて、特に問題なし。nkfcのmakeにめっちゃ時間がかかるくらい。


次にドキュメントのチュートリアルに進む。

$groonga -n t.db
> status
Aborted



おぉ・・・、なんだ・・・


あー、インストール失敗してた。MeCabをインストールした後に、Ubuntuのインストール説明のやつやってた。

以下、チュートリアル通り。

$groonga t.db
> status
[[0,1291307394.09009,0.000188872],{"alloc_count":126,"starttime":1291307387,"uptime":7,"version":"1.0
.4","n_queries":0,"cache_hit_rate":0.0,"command_version":1,"default
_command_version":1,"max_command_version":2}]



わいわい


jsonの配列の0番目の要素に、エラーコードや実行時間などの情報が入ります。jsonの配列の1番目の様子に、コマンドの実行結果が入ります。


とのことだけど、詳しくは良くわかんないですね。
と思ってソース見てみる。
proc_status関数で処理されてるけど、うーん、後回しでいっかな。
必要になったらまた見よう。


次、Siteという名前のテーブルの作成
ShortText型の主キー値を持ち、主キーの格納方法はHASH

> table_create --name Site --flags TABLE_HASH_KEY --key_type ShortText
[[0,1291309642.66916,0.049232948],true]



selectでテーブル内容表示。[0]はレコード数。テーブルを作っただけだから0だよね。カラムは2つ自動的に作られていて、_id=自動で割り振られる値を格納するカラム、_key=主キーを格納するカラム。

> select --table Site
[[0,1280378897.62847,0.000124],[[[0],[["_id","UInt32"],["_key","ShortText"]]]]]



カラムの追加。
Siteというテーブルにtitleというカラムを追加する。
COLUMN_SCALARは通常のカラムを示す

> column_create --table Site --name title --flags COLUMN_SCALAR --type ShortText
[[0,1291310090.51808,0.027044456],true]
> select --table Site
[[0,1291310100.15807,0.000200291],[[[0],[["_id","UInt32"],["_key","ShortText"],["title","ShortText"]]]]]



titleというカラムが追加されています。
ドキュメントはcommentカラムを追加するとあるから、間違ってますね・・・。こういうのってどうすればいいのかなぁ。自分で直せるのかな??APIrdにもtypo見っけたんだけど・・・。



続きはまた明日。




2010年11月8日月曜日

lftpを使う



流行に乗り遅れるのが俺のジャスティス!ってことで、今さながら、mixiのサンシャイン牧場なんかをやってみたり。
その流れで、mixiアプリを作ってみることに。


まずsakuraサーバをレンタルで借りました。
んで、次にftpがいるわけですが、gftpは同期ができないらしいんで、ubuntuに標準で入っているlftpを使用しました。


物理のかぎしっぽを参考にしました。
まず、sakuraのドメインにログインするための、ユーザ名とパスワードを自動で記憶させるために、



~/.lftprc というファイルを作成し,そこに set bmk:save-passwords 1 と記述します.これらを一気に行うには
$ echo 'set bmk:save-passwords 1' > ~/.lftprc



を行います。

この後、ログインした後に、bookmarkコマンドを打てば次から楽になります。


起動: $ lftp
ログイン: $ open site -u user


この後パスワードを聞かれるので入力


次に、$ bookmark add hoge
これで、次にログインするときは$ open hoge でOKです。


つぎに、sakuraのディレクトリ構成と同期をとるために、ローカルにDLします。
$ mirror


んで、sakuraではwwwというディレクトリがあってその下にupしたいファイルを置きます。
ファイルを置いたので、アップします。
$ mirror -R


ここで問題発生。実はコマンドを打ったカレントディレクトリがhomeだったため、意図しないファイルががんがんupされてしまいました。ぎゃー。
幸い、先にDLしていたので、サーバ側をざっくり削除してから$ mirror -R し直しました。
(本当は、mirror -R -e とするべきだったのだけど)

以上ー。
mixiのほうはマイミク取得ができるうようになっただけで、何作りたいとかは特にアイデアないっすねぇ・・・。





2010年10月4日月曜日

ラムダ式をそのまま実行



javascript - λ表記をDSLに

おー~すばらしい。
 実はラムダ式はよくわかってないんだけどw
ラムダ式って、確か今までの数式の書き方は一意な解釈ができない曖昧なものが多かったから、それを統一するために作ったのだったかなぁ??
 だけど、結局無理なことがわかって中断したけど、プログラム言語との相性がかなり良かった、という理解です。

 あと、これの一つ前の記事のpushdとpopdの話も有益でした。
danさんのブログは「面白くない」と言われることも多いけど、僕はやっぱり面白と思う。最初は「ミーハー」で見始めて、たまに自分がよく理解できない記事や、まわりが面白くないって言ってたりするのに影響されて、一時期ちょっと離れてたりもしたけど、もう一度噛み締めると、とても面白い。
 danさんの意見はちょっと独特だからだと思う。たぶん本人も意識して書いてるんじゃないかなぁ。昔の記事に、バークレーで普通のことを喋るとすぐにコイツは面白くない奴扱いされるって書いてあったし。
 





AutoItを使ってみた

 windows環境で仕事をするようになって、VBAやらWSHを作る機会が多いのですが、キー入力を送ることでwindowsの操作(と使いにくい社内ツール)を自動化できるsendkeyというのがあります。

 でもこれってウインドウを指定できないので、うっかり違うウインドウを立ち上げてしまったり、sleep時間が短くてウインドウの遷移が間に合ってなかった時などが、阿鼻叫喚。
目的と違うアプリウインドウにキーを入力されてしまうことがありました。

 でもAutoIt使うとwindowやクリック先のボタン、テキストの入力先を指定できるので安心です。windowがアクティブになるのを待ってからキーストロークを送信することも出来ます。
http://blog.livedoor.jp/blackcode/archives/1133644.html

 DLLを使用するだけでも作れますが、インストールして、AutoIt window infoでwindow情報を取得してやると、クリックするボタンを名指しで指定できるので便利です。

 もともとこれを探した理由は、とあるアプリのラジオボタンをキーボードで選択する方法がわからなかったからです。Tabを押しても移動できず困っていましたが、AutoItなら何番のボタンなのかをAutoIt window info で調べて指定することで選択することが可能になりました。

 秀丸やemacs、エクセルのようにキーボードマクロはあるのですが、どうも単純なsendkeyで記録されてる模様。
これが何とかなると便利なんだけどなぁー

2010年9月28日火曜日

モデルベース開発の分かり易い説明



 モデルベースなどについて分かり易く解説されているツールベンダーのブログがありました。
株式会社スムーズワークスです。


 分かり易いのは、ざっくり書いているから、というのもあるでしょうが、
日本語自体がとても魅力的で見習いたいと思いました。


以下、読んだ記事
実行可能な仕様
モデルベース開発は有用なのか?
機能安全セミナー in ESEC2010


 一通りは読みましたが、実際に自分の言葉でアウトプットしないと、抜け漏れがありまくるのは、Perlベストプラクティスで実証しちゃってるので、次の機会にブログに書きたいと思います。





2010年9月27日月曜日

Perl best practices[Perlベストプラクティス] 13章 エラー処理 13.11~13.3



例外が2つ以上関与する場合は、例外オブジェクトを利用する


 文字列ベースの例外には、既存の例外から、新しい例外を簡単に作成する方法がない。
例えば、特定の範囲外の整数を警告する例外と、あまりにも大きすぎる整数を警告する例外があるとする。
 これを例外オブジェクトを用いた場合、

#以下のような文を含むクラスがあったとする
croak( X::TooBig->new( {num=>$num, list=>$MAX_ALLOWED_VALUE} ))
if $num > $MAX_ALLOWED_VALUE;



以下のように派生クラスを作成することで、

package X::WaaayTooBig;
use base qw( x::TooBig );

#略
croak(X::WaaayTooBig->new( {num=>$num} ) )
if $num > $MAX_INT;



どちらの例外の場合でも、同じコードで補足できる。
つまり派生した例外を作成しても、例外を処理するコードを変更する必要がない。
if( X::TooBig->caught() ){

}

例外クラスは派生端から順に補足する


if( X::TooBig->caught())とした場合、X::WaaayTooBig例外が送出された場合、真となる。
X::waaayTooBigはX::TooBigを継承しているから。

例外クラスを自動的に構築する


 例外クラスは便利で保守性が高いが、コードを書くのが難しい。
Exception::Classモジュールを使用するとよい。




2010年9月23日木曜日

Perl best practices[Perlベストプラクティス] 13章 エラー処理 13.7~13.10


エラーメッセージは「何が、なぜ、どこで、いつ」を書く

サブルーチンを使用する側にたって、メッセージを書くこと。そっけないメッセージはダメ。
問題の全容、その理由、発生した場所、呼び出し元での失敗したソース行を示すこと。

エラーメッセージの分かり易い解説をドキュメントに書く

コードから生成される可能性のある例外、警告をドキュメントにすることが大事。perldiagの標準マニュアルは平易な文章でエラー内容を記述してあり、お手本になる。

例外オブジェクトを使用する

perl5.005以降では、ブレスされた参照(オブジェクト)をdieまたはcroakに渡せるようになった。
オブジェクトを例外として使用する利点は2つある。
 1つ目は、例外オブジェクトを型で検出できること。例外クラスのcaught()メソッドを使用する。

#get_number()内で以下のような記述があるとする
#X::TooBigという例外クラスをcroakに直接わたす。
#croak(X::TooBig->new( {value=>$num, range=>[0, $MAX_ALLOWED_VALUE]} ) )
#if $num > $MAX_ALLOWED_VALUE;

  my $value = eval { get_number() };

  if ($EVAL_ERROR) { #例外を補足。例外には例外オブジェクトが入っているかもしれない。
    if ( X::TooBig->caught() ) {#例外オブジェクトがX::TooBigクラスに属している
      my @range = $EVAL_ERROR->get_range();
      $value = $range[-1];
    }
    elsif( X::TooSmall->caught() ) {#例外オブジェクトがX::TooSmallクラスに属している
      $value = $EVAL_ERROR->get_value();
    }
  }
  else {
    croak( $EVAL_ERROR);
  }

  #例外オブジェクト内のcaughtメソッドを抜粋
  sub caught {
    my ($this_class) = @_;

    use Scalar::Util qw( blessed );
    return if !blessed $EVAL_ERROR;        #EVAL_ERRORはオブジェクトでないならfalseをリターン
    return $EVAL_ERROR->isa($this_class); #$EVAL_ERRORがオブジェクトで、かつ、このクラスに属しているなら真
  }

二つ目のメリットは、複雑なデータ構造を例外オブジェクトに詰め込んで例外ハンドラに渡せること。
例外ハンドルには、実際のハンドルが含まれているため、再試行できる可能性がある。

croak( X::EOF->new({ handle => $fh }) )
if $fh->eof();

2010年9月22日水曜日

例外クラスがやっとわかった・・・・



例外クラスがわからない、という記事を書いていたら急にわかった・・・
明日書きます。疲れた・・・。


はじめてのPerlも読んでるんですが、図書館から借りてる本(パケット解析)も読まないといけないので悩む。パケット解析の本は、最初の基礎編の章をよんで、OSI参照モデルと、ハブやスイッチ、ルータの特徴を復習しました。


自分は組込み屋なんで関係ないんですが、まぁ趣味・・・ってことで。
仕事に関係あることも勉強しないとなぁ・・・電子回路とか。




2010年9月21日火曜日

初めてのPerl 第5版 読み始めました



 perl best practicesの13章を記事にするのに手間取ってて、現実逃避に読んでみたり。
知らないこともあって、やはりこういう本はちゃんと読まないとなーと思いました。
まだ40ページほどしか読んでないですが、知ってる内容が多いのですぐに読めるとは思います。
(自分は読むのが遅いタイプなので、知ってる内容でも1頁30秒はかかるのが辛い)

 あと、ラクダ本も買ったけど、二部構成なの知らなくて後半だけ買ってしまったw
1部も買わないと・・・。





2010年9月19日日曜日

Perl best practices[Perlベストプラクティス] 13章 エラー処理 13.1~13.6



2つの原則



  1. 検出可能なランタイムエラーをすべて検出し、分類し、報告する

  2. 検出したエラーを無視できないようにする



失敗したときは、フラグなどの値でなく例外を送ることでプログラムを終了させる。


 呼び出したサブルーチンが失敗した場合、そのサブルーチンがエラーを表す値をreturnしても、呼び出し元がそのエラーを無視したり、適切に処理できなければ意味がない。
 フラグ等を使用することの欠点は2つある。
 1つ目は、開発者(サブルーチンを使用して、呼び出し元コードを欠いている人)がエラーを無視することに慣れること。
 2つ目は、何重にもネストして呼び出されたサブルーチンは、一番最初の呼び出し元まで、バケツリレーのように、エラーを適切に処理&上位に送信、を繰り返さなければならない。これでは、すべてのサブルーチンのコードが肥大することになる。バケツリレーでエラーを運ぶのではなく、失敗したサブルーチン内で例外を出すこと。
 この例外をスルーするにはevalを明示的に使用する努力が呼び出し元に要求される。そして、この例外を一番先頭の呼び出し元で補足することで、各ルーチンすべてにバケツリレー処理を書く必要がなくなる。バケツリレー処理を書かないで済む理由は、例外は自動的にバケツリレーをして、上位呼び出し元に運んでくれるから。そして最上位呼び出し元で、明示的に例外が補足されないとプログラムは終了する。
 

 以下のように、ネストではなく、並列に並んだ処理のすべての値をチェックしたい場合も、例外を使用したほうがいい。
コードがすっきりする。



SOURCE_FILE:
for my $filename (@source_files) {
my $fh = locate_and_open($filename);
next SOURCE_FILE if !defined $fh;  #このエラー処理を無くしたい

my $head = load_header_from($fh);
next SOURCE_FILE if !defined $head; #このエラー処理を無くしたい

print $head;
}

for my $filename (@directory_path) {
#例外が出されたときは、evalによって無視されるので、nextと同じ動作になる。
eval {
my $fh = locate_and_open($filename); #サブルーチン内でエラー処理。その際、例外を出すようにする。
my $head = load_header_from($fh);  #サブルーチン内でエラー処理。その際、例外を出すようにする。
print $head;
}
}




組み込み関数のエラーで、例外を出させるようにする


Fatalモジュール

 組み込み関数や、サブルーチンの名前を指定すると、falseを返すのではなく、例外が出されるように黒魔術がかけられる。
 その際、:voidという特殊なマーカを使用しない。これは、voidコンテキスト以外ではFatalが例外を送出しないようにするためのマーカ。紛らわしいので使わない。


やっかいなsystem組み込み関数


 systemコマンドは、成功するとfalseを返し、失敗するとtrueを返す。
これはFatalも効かない。POSIX標準モジュールのWIFEXITEDサブルーチンを使用するか、Perl6モジュールを使用する。(Perl6では、system関数は成功するとture、失敗するとfalseを出すように修正される)



use POSIX qw( WIFEXITED );

WIFEXITED(system $cmd)
or croak "Couldn't run: $cmd ($OS_ERROR)";



use Perl6::Builtins qw( system );

system $cmd
or croak "Couldn't run: $cmd ($OS_ERROR)";



回復可能なエラーでも例外を送出する


 回復可能なエラーではundefを使用した方が良いと思われるが、ここでも例外を送出したほうが良い。理由は、今までと同じで、開発者がエラー処理をするとは限らないから。(フラグ等、戻り値は無視される)


例外は呼び出し元から出す


 サブルーチン内でエラーを返されても役に立たない。呼び出し元からエラーを送出すること。
理由は、あなたが作ったサブルーチンを使用している開発者が知りたいのは、あたたのコードのどこで問題が検出されたのかではなく、自分たちのコードのどこに問題があるか、だから。


dieではなくcroak( )を使用する

 呼び出されたサブルーチンの立場でエラーを報告するdieを使って例外を出すのは、呼び出されたサブルーチンに落ち度があるときだけ。そのときのメッセージは常に"Internal error:"で始まるようにすること。その他の場合はcroak( )を使用すること。
 しかし、内部エラーであってもcroak( )を使用したほうが良いときもある。例えば、内部エラーを呼び出し元の視点に立って報告することが出来る点。さらに重要なのはCarpモジュールにコマンドラインから使用できる'verbose'オプションがあること。このオプションを使用してプログラムを実行すると、croak( )の呼び出しごとに、エラーメッセージと完全なバックトレースが提供される。


組み込みwarnではなく、carp( )を使用する

 dieと同じ理由。警告でもcarp( )を使用すること。





2010年9月16日木曜日

qr{ }のネストについて





#数字を補足する正規表現$NUMBERを作成する
Readonly my $DIGITS => qr{ \d+ (?: [.] \d*)? | [.] \d+ }xms;
Readonly my $SIGN => qr{ [+-] }xms;
Readonly my $EXPONENT => qr{ [Ee] $SIGN? \d+ }xms;
Readonly my $NUMBER => qr{ ( ($SIGN?) ($DIGITS) ($EXPONENT?) ) }xms;


 このネストが、なぜ遅いのか理解できました。
まず、なぜ理解できなかったのかというと、$DIGITS => qr{ ... } の時点で、$DIGITSにはプリコンパイルされた値が入っているというのが分かってなかったからです。
 $NUMBERで使用している$DIGITSなどはすでにコンパイルされているので、これを逆コンパイルして、文字列にもどしてから、$NUMBERに代入される正規表現全体で再度コンパイルしています。
 「逆コンパイル」ということは、そりゃぁ、元の文字列にならないこともあるよなぁ、という感じです。


Deparseしたら見れるのかな?と思って
$perl -MO=Deparse,-x7 qr.pl
としてみたけど。特に何も見れませんでした。


ついでに以下ような変換が見れました。



#before
if(A =~ B){ ... }

#after
A =~ B and do{ ... }

#before
for (my $i=0; $i<10; $i++) {
$i++;
}

#after
while ($i < 10) {
++$i;
}
continue {
++$i
}



初めcontinueは何だろうと思ったけれど、forはブロックの最後に反復子がインクリメントされるから最後に1回だけインクリメントしないといけないですよね。


面白いなぁ。





2010年9月14日火曜日

Perl best practices[Perlベストプラクティス] 12章 正規表現 12.22~12.24

バックトラックを防ぐリファクタリング

共有部分を括り出す

m{
    with \s+ each \s+ $EXPR \s* $BLOCK
  | with \s+ each \s+ $VAR  \s* in \s* [(] $LIST [)] \s* $BLOCK
  | with \s+ [(] $LIST [)] \s* $BLOCK
}xms

 上記のコードは頻繁にバックトラックが起こる。例えばeachのeでマッチングの失敗したとすると、2つ目の選択肢に移り、またwith \s+でマッチングを試み、そしてまたeachで失敗して、3つ目の選択肢でまたまたwith \s+でマッチングしようとする。
 この手の正規表現をPerlが内部的に最適化してくれると良いけれど、それをPerlに任せると処理コストが高すぎるので、自分でリファクタリングする必要がある。
 リファクタリングの内容は、共通パターンを括り出すとことだが、実際の手順としては、まず選択肢の集合を(?:)で囲む。そして、共通部分を外に出す。この2ステップを踏む。


m{
    with \s+ each \s+ $EXPR \s* $BLOCK
  | with \s+ each \s+ $VAR  \s* in \s* [(] $LIST [)] \s* $BLOCK
  | with \s+ [(] $LIST [)] \s* $BLOCK
}xms

#まず選択肢集合を括弧で囲む
m{
    (?: with \s+ each \s+ $EXPR \s* $BLOCK
      | with \s+ each \s+ $VAR  \s* in \s* [(] $LIST [)] \s* $BLOCK
      | with \s+ [(] $LIST [)] \s* $BLOCK
    )
}xms

#共通部分を外に出す。頭のwith \s+ とお尻の\s+ $BLOCK。
m{
    with \s+
    (?: each \s+ $EXPR
      | each \s+ $VAR  \s* in \s* [(] $LIST [)]
      | [(] $LIST [)]
    )
    \s* $BLOCK
}xms

#まだeach \s+が括り出せるので、括弧で囲む。
m{
    with \s+
    (?: 
        (?: each \s+ $EXPR
          | each \s+ $VAR  \s* in \s* [(] $LIST [)]
        )
      | [(] $LIST [)]
    )
    \s* $BLOCK
}xms

#each \s+を外に出す。
m{
    with \s+
    (?: each \s+
        (?: $EXPR
          | $VAR  \s* in \s* [(] $LIST [)]
        )
      | [(] $LIST [)]
    )
    \s* $BLOCK
}xms


 この方法の欠点として可読性の低下が挙げられるが、正規表現を定数に変換するなどして対処すること。

(?>)でさらにバックトラックを無くす

先ほどの、選択肢集合から共通部分を括り出す方法は、外に出されたプレフィックスに関しては当然バックトラックは起こらないけれども、外に出したサフィックス(接尾辞)でマッチングに失敗した場合は、選択肢集合にバックトラックして、選択肢集合の中でありとあらゆる可能性を走査するため、バックトラックが頻発する。
 もし、マッチングが最初から最後まで成功するか、まったくしないかの2パターン(相互排他関係)ならば、サフィックスでマッチングに失敗した際に、バックトラックする必要はない。このことをPerlに教えるために(?>)を使用する。
 先ほどの例では、(?:)を(?>)に置き換えるだけでいい。


 この(?>)の説明で紹介されている例が興味深かった。それは、ほぼ正しい入力によって、正規表現のマッチングが失敗する場合は、バックトラックが頻発すること。紹介されている例は、コンマで区切られたリストとマッチする正規表現でした。


my $str =~ m{ [(]
           $ITEM       #少なくとも1つは要素があるようにする
           (?:         #コンマと要素のペア。
                ,
                $ITEM
           )*
           [)]
         }xms;

 このとき、入力されたリストが閉じ括弧が無かったらどうなるか。まず[)]でパターンマッチに失敗した後、前に成功したマッチ(?:,$ITEM)*の最後の繰り返し部分、つまり、リストの最後のコンマと要素の組1つとのマッチ成功を捨てて、その分を[)]とのマッチに使用する。しかし、このマッチは当然失敗する。そうすると、バックトラックして、また一つ前のマッチ成功を捨てて[)]とのマッチを試みる。これは、リスト要素の数だけ繰り替えされる。
 これを防ぐために、(?>)を使用するが、先ほどの説明通りに、単純に(?:)を(?>)に置き換えてはいけない。なぜなら*を含めて、パターンの方法は1つとしなければならないから。(?> (?: , $ITEM)* )とする。
 まとめ。2つの部分パターンXとYが、照合する文字列に対して相互排他の関係にあるならば、
  • X | Y は (?> X | Y )
  • X* Y は (?> X*) Y
とすること。

文字列比較

固定文字列との照合は|ではなく、個別にeqする
初めから照合したい文字列が決まっている場合、(?: A | B | C)より、($hoge eq 'A') || ($hoge eq 'B') || ($hoge eq 'C')のほうが20%程高速であるし、コードも読み易くなる。
固定文字列の数が多い場合や、数が決まっていない場合

 選択肢の数が多い場合や、数が決まっていない場合は、正規表現を生成するほうが適していると思うかもしれないがeqを使用したほうがすっきりする。ただし低速。


Readonly my $EXIT_WORD => join '|', @EXIT_WORDS;
last COMMAND if $cmd =~ m{\A (?: $EXIT_WORD ) \z}xms;

use List::MoreUtils qw( any );
last COMMAND if any { $cmd eq $_ } @EXIT_WORDS;


今後読む予定の本



 順番がおかしいかもしれませんが、ベストプラクティスを読んだあとは、以下の順で体系的に学んでいきたいと思います。



プログラミングPerlを読破した後は、現状では意味不明の実用Perlプログラミングに挑戦してみます。

 すぐわかるオブジェクト指向Perlは、初心者向け内容っぽいのですが、ひどいコードからリファクタリングしていく様子がわかるらしいので、ちょっと読んでみたいと思います。たぶん上記の本まで読んでると、多少天狗になってそうな気がするので、基本の本を読んでへし折っとこうと思います。

 後は、順不同かな?


 モダンPerl入門とデータマンジングとPerl Cookbookとかもありますね。マスタリングPerlも読んだほうがいいのかなぁ・・・。
 ベストプラクティスは読むのに1ヶ月くらいかかったので、他のはもっと早く読みたいな・・・





2010年9月13日月曜日

理解できてないなぁ



 Perlベストプラクティスの正規表現の部分があんまり理解できてなくて、書くのにすごい時間がかかってます。本自体はどんどん先を読んでいて、もう読み終わりそうなんですが・・・。正規表現あたりまでは黙読だったのですが、それがまたいけなかった感じです。
 黙読すると、知らないうちに脳が詳細を理解するのを避けていて、理解した気になって次に進んでしまうようです。そこで、シャーペンで自分の言葉で、本にぐりぐり書くようにしたら、大分、詳細から逃げずに理解できるようになりました。
 ただ、それでもブログに書くときになると、解らなかったことがわかるようになるので(どうも集中力が違うみたい)、やっぱりアウトプットは大事だなぁと思いました。

 以下の言葉が実感としてわかります。



2.インプットではなくアウトプットに基づいて目標を設定する

 「3時間がんばろう」というのは、まやかしです。「3ページ書こう」にしましょう。
 文章力向上のための10の基本的心構え






Perl best practices[Perlベストプラクティス] 12章 正規表現 12.18~12.22



テーブルから正規表現を生成する


 前節では三項演算子を用いたテーブル(っぽくみえる表記)で、正規表現のパターンマッチを行ったが、テーブルは正規表現を生成するためにも使える。あるテーブルを正規表現を使って検索する場合、テーブルから正規表現を生成する。

#変則的な複数形のテーブル
my %irregular_plural_of = {
    'child'             => 'children',
    'brother'        => 'brethren',
    'money'          => 'monies',
    'mongoose'    => 'mongooses',
    'ox'                => 'oxen',
    'cow'              => 'kine',
    'prima donna' => 'prime donne',
    'tooth'            => 'teeth',
    'toothfish'      => 'toothfish',
};

#正規表現の生成
my $has_irregular_plural = join '|', map {quotemeta $_} reverse sort keys %irregular_plural_of;

while (my $word = <>) {
    chomp $word;
    if ($word =~ m/\A ($has_irregular_plural) \z/xms) {
        print $irregular_plural_of{$word}, "\n";
    }
    else {
        print form_regular_plural_of($word), "\n";
    }
}

 ここでは2つの作業が必要になる。
 1つ目は、reverseを用いていること。|でつないだ正規表現は、左から順に評価されるので、作成された正規表現が....| tooth | toothfish | ... の順番だと、パターンマッチの部分で、ハッシュテーブルのtoothfishは正規表現のtoothとマッチしてしまう。このため、生成される正規表現は、toothfishがtoothより先に評価されるようにreverseを使用する。
 2つ目は、quotemetaを使用することで、メタ文字をエスケープすること。'prima donna'も'prima\ donna'になり、/xフラグがセットされていても問題なく処理できるようになる。
 このテーブルを用いた正規表現の生成は、テーブルと正規表現の一貫性を保証できるし、テーブルにキーと値のペアを追加するだけで、正規表現も拡張される点でメリットがある。
 決して、以下のように、元のテーブルのキーをコピーしただけの正規表現を作ってはいけない。これは作業が面倒だし、タイプミスも混じり込みやすい。


my $has_irregular_plural = qr{
    child | brother  | mongoose
  | ox    | cow      | money
  | tooth(?:fith)?
}xms;

複雑な正規表現は単純なパーツに分ける


#数字を補足する正規表現$NUMBERを作成する
Readonly my $DIGITS   => qr{ \d+ (?: [.] \d*)? | [.] \d+         }xms;
Readonly my $SIGN     => qr{ [+-]                                }xms;
Readonly my $EXPONENT => qr{ [Ee] $SIGN? \d+                     }xms;
Readonly my $NUMBER   => qr{ ( ($SIGN?) ($DIGITS) ($EXPONENT?) ) }xms;

 ここで、一つqrを使った際のデメリットを説明する。それはqrが適用された正規表現内に、qrが適用された正規表現があると、パフォーマンスが低下するかもしれないこと。$EXPONENTや$NUMBERが当てはまる。理由は、要素である正規表現を展開する際に、文字列に逆コンパイルされてから展開され、最後に再コンパイルされるが、文字列に逆コンパイルする際に、最適化されず効率の悪いパターンを生成する可能性があるため。
 代わりにq{ }またはqq{ }を使用すると、単純な文字列として、製作者が作成した正規表現がそのまま展開される。ただし、q{ }、qq{ }を使うと、エスケープ文字や、部分パターン全体を一つの項目として扱ったりするので、複雑な場合は、qr{ }を使うことが推奨される。
 あまりにも複雑な場合は、Regexp::Assembleを使用してみる。

既存の正規表現をまとめたモジュールを使用する


 正規表現の車輪の再発明はとても多い。これはRegexp::Commonモジュールを使用することで楽になる。
前節の、数字にマッチする正規表現は以下のように書ける。


use Regexp::Common;

Readonly my $NUMBER => $RE{num}{real}{-keep};#いつも$REというハッシュを基に作成する。

単一文字を|でつなぐのではなく文字クラスを使用する

以下の1番目のように書くとパフォーマンスの問題が生じる。2番目のようにすること。また、複数文字の共通点を文字クラスに統一することも推奨される。

if($cmd !~ m{\A (?: a | d | i | q | r | w | x ) \x}xms)

if($cmd !~ m{\A [adiqrwx] \z}xms)


2010年9月12日日曜日

日本の Perl ユーザのためのハブサイト



日本の Perl ユーザのためのハブサイト



YAPC::Asia 2008 で Michael Schwern は「SEO に有効な独自ドメインを取って、もっと Perl 初心者が集まりやすい nice な Perl の情報を集めたサイトを作れ!」といったので Perl-users.jp ドメインを取って、ここに Perl-users.jp を開始します。(2008年5月20日)




うーん、まだまだ普及してませんね・・・・。





正規表現に範囲演算子を適用する



naoyaグループ - naoyaの日記 - if /regexp/../regexp/
以下、はてなブックマークのmiyagawaさんのコメント



miyagawa "間に含まれる文字列" というわけじゃなくて if $a .. $b で "$a がtrue になってから $b が true になる間まで" になる、けどこんなんめったに使わないな。。 perldoc perlop






Perl best practices[Perlベストプラクティス] 12章 正規表現 12.13~12.17



ターミネータ(区切りとなる文字)がわかっている場合は、.*ではなく補完文字クラスを使用する


.*という正規表現はとても処理が重くなる可能性がある。

 例えば以下の例では、最初の.*は隣の%ともマッチし、その後、文字列の終わりまでマッチする。その後、次の正規表現である%を文字列の続きから探そうとするが、すでに.*によって文字列の最後までマッチが進行しているので、%がマッチする箇所は存在せず、マッチは失敗する。このとき、正規表現エンジンは、%がマッチに失敗した箇所から一文字後ろの文字に着目して、マッチを試みる(バックトラック)。マッチが成功するまでこの一文字ずつのバックトラックを繰り返すため、効率はとても悪い。



$sorce =~ m/\A (.*) % (.*) & (.*) /xms;



とりあえず.*を.*?にしてみても、処理は改善されない

 このような正規表現を作成して、処理がとても遅い場合、とりあえず.*を.*?にして問題を解決してみようと思うかもしれないけれど、たぶんあまり上手くいかない。
 .*?は.*とは違い、マッチする文字列をできるだけ少なくしようとする「けちけちした繰り返し」ではあるが、これは結局、文字の先読みをしているので、ターミネータ(区切り文字。この場合、%と&のこと)が単一文字よりも複雑である場合は、処理が遅くなる可能性がある。


.*と.*?はエラーを隠蔽する可能性がある

 正規表現をみる限り、おそらく製作者はデータに%と&が一つずつ含まれていることを想定しているが、%と&が文字列に複数含まれていても.*と.*?はエラーにならない。文字列が...%....%....&...となっていた場合、2回目の%までは、.*か.*?にマッチする。そうしないと、全体でマッチが成功しないから。


補完文字クラスを使用する

 上記の理由から、解決策は%や&といったターミネータ以外を表現する正規表現を使用すること。つまり、以下のようにする。



$source =~ m/\A ([^%]*) % ([^&]*) & (.*) /xms;


 補完文字クラスを使用することにより、.*?の先読みも、.*のバックトラックも行われない。かつ余分なターミネータが含まれていた場合、マッチに失敗するようになる。
 また最後の(.*)に着目して欲しい。これは常にマッチに成功するので、バックトラックは行われない。逆に、(.*?)が正規表現の最後にあるのは常に間違いである。(.*?)は「けちけち繰り返し」なので、(.*?)の後に正規表現がないならば、最もケチなのは、まったくマッチをしないことになる。そのため、常にNothingにマッチし、成功する。このため、最後の(.*?)は余計であるか、書き手が(.*?)の挙動を理解していないために、書き手の意図とは異なったものであるか、\zアンカーの付け忘れのいずれかである。(\zアンカーがあれば、(.*?)は文字列の最後までマッチさせる義務が生じる)


補足のための括弧は、補足のためだけに使用する


単純なグループ化のために括弧を使用しない

 グループ化には(?:)を使用すること。後方参照する必要がないならば、こっちのほうが、余計な補足をしなくていい。余計な補足は、余計な処理をする以上に、保守をする人間に誤った意図を伝えることが問題となる。例えば、以下の例では、perform_cleanup()サブルーチンの中で補足された文字が使われていないか探し回るハメになる。(そして結局使われていないことがわかる)



if ( $cmd =~ m/\A (q | quit | bye | exit) \n? \z/xms ) {
perform_cleanup();
exit;
}



(補足した正規表現が、そのままサブルーチンの中で使えるなんて知らなかった)


補足変数$1,$2などは、パターンマッチが成功した場合にのみ使用する


 パターンマッチが成功したかどうかは、$1,$2などが定義されているかをチェックするのではなく、常に以下のようにパターンマッチ全体が成功したかどうかを評価すること。



if ($full_name =~ m/\A (Mrs?|Ms|Dr) \s+ (\S+) \s+ (\S+) \z/xms) {
($title, $first_name, $last_name) = ($1, $2, $3);
}


 なぜなら、パターンマッチに失敗した場合、$1などの変数には何も代入はされないが、以前に成功したパターンマッチの結果が保持されているから。


補足変数をそのまま使用せず、適切な名前を付ける


 $1,$2などは$_[0],$_[1]などと同じく、何も意味のない値で保守に苦労する。また$1と$2の間に新しく補足変数を追加した場合、従来の$2は$3になるため、その後のコードをすべて書き直す必要がある。
 このようなことをしないためにも、適切な名前をつけた変数に代入するようにすること。この際、リストコンテキストを用いて補足した値を直接リストに渡すことができる。これは、常に補足した値をすべて返すため、変数に代入し忘れることがない点で推奨される。



my ($opt_name, $operator, $opt_val, $comment)
= $config =~ m/\A (\S+) \s* (=|[+]=) \s* ([^;]+) ; \s* \# (.*)/xms;


 ただし、次節で説明するように、/gc修飾子を使用する場合は適していない。


トークンごとのパターンマッチ


発見したトークンを置換で文字列から削り取るのは処理が遅い

 文字列をトークンに分解する際には、以下のように少しずつ文字列を削り取っていくのが常套手段だが、処理が遅くなってしまう点が良くない。



while (length $input > 0) {
if ($input =~ s{\A ($KEYWORD)}{}xms) {
my $keyword = $1;
push @tokens, start_cmd($key_word);
}
else if ($input =~ s{\A ($IDENT)}{}xms) {
my $ident = $1;
push @tokens, start_ident($key_word);
}
else if ($input =~ s{\A ($BLOCK)}{}xms) {
my $ident = $1;
push @tokens, start_block($key_word);
}
else {
my ($content) = $input =~ m/ \A ([^\n]*) /xms;
croak "Error near: $content";
}
}


削らずに前回位置を記憶するだけにする


 このため、マッチした部分を置換によって削るのではなく、/gcフラグをセットし、前回マッチした位置を記憶し、組み込み関数pos()を使用して、記憶した位置から次のトークンを探すようにする。/gは前回位置を記憶し、/cはマッチに失敗しても位置記憶(前回マッチに成功した位置)をリセットしないようにする。



cフラグを伴って\Gを使う局面は、典型的にはtokenizerのようにあるマッチが失敗したときに別のマッチを試したいときです。
perlfaq6 - Regular Expressions ($Revision: 1.31 $, $Date: 2005/03/27 07:17:28 $) 訳出 2005/11/2



 さっきのコードと違うところは、while文の終了条件にposを使用していること。\A(削り取った文字列の先頭)が\G(前回マッチした終わりの位置)になっていること。置換は必要ないので、s{ }{ }ではなくm{ }であること。



pos $input = 0;

while (pos $input < length $input) {
if ($input =~ m{\G ($KEYWORD) }gcxms) {
my $keyword = $1;
push @tokens, start_cmd($key_word);
}
else if ($input =~ m{\G ($IDENT)}gcxms) {
my $ident = $1;
push @tokens, start_ident($key_word);
}
else if ($input =~ m{\G ($BLOCK)}gcxms) {
my $ident = $1;
push @tokens, start_block($key_word);
}
else {
my ($content) = $input =~ m/\G ([^\n]*) /gcxms;
croak "Error near: $content";
}
}



三項演算子の正規表現版

 この場合、条件文が同じ@tokensに値を設定しているので6章で説明した三項演算子が有効である。



#略
push @tokens, (
$input =~ m{ \G ($KEYWORD) }gcxms ? start_cmd($1)
#略



gcフラグはリストコンテキストで使わない

 /gフラグはスカラコンテキストとリストコンテキストでは動作が異なる。リストコンテキストでは、正規表現にマッチした部分文字列がリストとして返されるようになる。





2010年9月11日土曜日

Perl best practices[Perlベストプラクティス] 12章 正規表現 12.1~12.12




問題にぶつかると「正規表現を使えばいい」と考える人がいる。
そして、問題を2つ抱えることになる。
--Jamie Zawinski




常に/xフラグを使用する。


 これは、確実につかう。JScriptでもXRegExp使ってます。
Xを使うことで、ホワイトスペースが無視され、#も使用できる。そのため、ギチギチに詰められた正規表現である必要がなくて、意味のある単位ごとに、スペースで分けることができる。
 もっと分かり易くするには、意味のあるグループごとに改行して、インデントを付け、コメントをつける。


常に/mフラグを使用する


 メタ文字^$は任意の行の先頭と末尾にはマッチしない。ほとんどのUnixユーティリティ(sed,grep,awk等)はもともと行思考なので、^$は行の先頭と末尾にマッチするが、Perlではそういった意味はもたない。(JavaScriptでもね!)

 Perlでは文字列全体の先頭と末尾を表す。これはプログラマーが通常求める自然な動きではない。/mフラグを使用することで、^$が行単位でマッチするようにすること。


文字列の境界に\A\zを使用する


 本当に文字列の先頭と末尾を指定したいときに、/mフラグを外して^$を使用してはいけない。/mは常に使用すること。
 代わりに\A\z(小文字のz)が文字列の先頭と末尾を表現してくれる。\A\zは/mフラグの有る無しに関わらず使用することができる。


\Z(大文字)を使わない


 \Zは行ベース入力の際に便利だが、\z(小文字)と見分けが付きにくいので、使用しない。\Zは改行とそれに続く末尾にマッチするので、\n? \z を代わりに使用すること。


常に/sフラグを使用する


 衝撃的なことにドットは改行にはマッチしない(他の言語でもそうだけど)。これは自然なプログラマの意図とも外れるし、^$との併用でも弊害がでる。例えば{\A (.*?)^}としても^の前は改行であるはずなので、ドットとはマッチしない。
 /sフラグを使用することで、ドットは改行とマッチするようになる。本当に改行とマッチさせたくない場合は[^\n]*とすればいい。


自動的に/xmsフラグを挿入する


Regexp::Autoflagsを用いる


正規表現が複数行にまたがる場合は、/.../ではなく、m{ }を使用する


 //を使用すると、正規表現が見にくくなる。またxフラグを使用した場合に、問題が生じる。
以下の正規表現はうまく動かない。



m/
set \s+ #キーワード
($IDENT) \s* #Name of file/option/mode
= \s+ #リテラルの=
([^\n]*) #Value of file/option/mode
/xms;


 理由はコメント文だと思われている#Name of file/のスラッシュが正規表現の終わりだと判断されているため。コメントはパーサが正規表現を終了したと判断した後に、初めてコメントになる
 {}を正規表現の開始終了のデリミタとして使用すること。{}を使用しても同じ問題が発生するが、コメント文の中に{}が現れる可能性は/よりもずっと低いし、それによって生成されるエラーメッセージはスラッシュのときよりも遥かに分かり易い。
 ただし、mapなどのリスト演算ブロックではスラッシュを使用した方がコードが見やすい。



my @counts = map { m{(\d{4,8})}xms } @count_reports; #中括弧が3回もでてくる
my @counts = map { m/(\d{4,8})/xms } @count_reports;



エスケープされたメタ文字ではなく、文字クラスを使用する.


 バックスラッシュでエスケープされたメタ文字は、とても見づらい。このため、メタ文字を一文字で構成される文字クラスとして表現する。
\.ではなく、[.]とする。


処理速度に問題がある場合は、名前付き文字を使用する

 perlのバージョンによっては、処理速度に問題がある場合があるので、ベンチマークで問題が発覚した場合には、4章で説明した、名前付き文字を使う。
ただし、/xフラグを使用している場合は、名前付きで空白を指定しても、空白は空白なので、ムシされることに注意する。この場合は、文字クラスを使用する。



use charnames qw( :full );

$name =~ m{ harry [\N{SPACE}] s [\N{SPACE}] truman #harry s truman
| harry [\N{SPACE}] j [\N{SPACE}] potter #harry j potter
}ixms;



[A-Z]等、列挙された文字クラスではなく、Unicodeに準拠する名前付き文字クラスであるプロパティを使用する


 [a-Z]のような文字クラスはASCIIのアルファベットのみとマッチし、Unicodeのアルファベットにも、Latin-1にもマッチしない。Perl5.6以降の正規表現では\p{}エスケープがサポートされており、Unicodeのプロパティを完全に利用できる。[A-Z][A-Za-z]は、\p{Uppercase}\p{Alphabetic}と表せる。
 また言語に依存しないプロパティを使用することもできる。



Readonly my $PERL_IDENT => qr/ [A-Za-z_] \W* /xms;
Readonly my $PERL_IDENT => qr/ \p{ID_Start} \p{ID_Continue}* /xms;



 得に便利なプロパティがあり、それはドットメタ文字に対応する\p{any}
コードがとても読み易くなる。


ホワイトスペースが規則通りに入力されることを期待しない


 データへの入力としてホワイトスペースが要求されている箇所には\s+を、ホワイトスペースが入力される可能性があるところには\s*を使用する。





2010年9月10日金曜日

Perl best practices[Perlベストプラクティス] 11章 参照



サーカムフィックス(circumfix)逆参照を使わない


 サーカムフィックス(circumfix)逆参照とは、コレ=>${$list_ref}[0]
 代わりに矢印をつかって、$list_ref->[0] とすること。矢印のほうがすっきり書けるし、2重引用符で文字列に展開できるという利点がある。また、逆参照のほうは、中括弧や、プレフィックス$を忘れてもコンパイルエラーにならないというデメリットがある。

 スライスをする場合は、逆参照を使う他ない。



#下記のコードは同じ意味になる。
#$list_refが$で始まるので、[]はスカラーコンテキストで評価される。
my ($from, $to) = $list_ref->[0, -1];

my ($from, $to) = ($list_ref->[-1], undef);




逆参照しか選択の余地がないときは{}で囲む


 $$$stack_ref[0]とかはダメ。


シンボリック参照を決して使用しない


 シンボルを使うと、型グロブを通じて、現在の名前空間のパッケージ変数を参照する。
 パッケージ変数を使用する必要はほぼないはずで、大体レキシカルハッシュで代用できる。


双方向参照のデータ構造では、weakenを使用する


 双方向参照では、片方がスコープを外れたりして、削除されても、もう片方から参照されているのでガベージコレクションの対象にならず、メモリが開放されない。
 そこで、weakenを使用すると、参照カウントが1減るが、参照自体は残すことができる。片方がスコープから外れると、参照カウンタは0になるので、両方の参照ともに正しく消滅する。





2010年9月9日木曜日

はてなブログで日付が変わるのは翌日朝6時



なんか不思議だなぁと思っていたのですが、
デフォルト設定は翌日の朝6時のようですよ。

朝6時までに書くことは、昨日の日記でしょ?ってコンセプトのようです。
へぇー





Perl best practices[Perlベストプラクティス] 10章 IO 10.11~10.18



*STDINが本当に必要な時以外は<*ARGV>を使用する。


*STDINは、現在標準入出力に用いているものを常に指すわけではないし、ファイル名を指定しなければ(open *STDIN '<' $filename or croak...)、コマンドラインで指定されたファイルから・・・という意味にはならない。

*STDINは常に0番目のファイルディスクリプタを指す。デフォルトがターミナルであればいいが、パイプやファイルの中身を読んで、内容から他のデータを読み込みにいく等はできない。(そのファイル自体を指す)


普段使用している*ARGVを用いるほうがいい。(*ARGVは<>で表せる)



while (my $line = <>) {
print substr($line, 2);
}



print文では常にファイルハンドルを中括弧で囲む


レキシカルファイルハンドルを中括弧で囲むと、目につき易くなる。
以下のコードが、コンマを忘れただけだと思われないようになる。

print {$file} $name, $rank, "\n";

IO::Handleモジュールを使用してprintサブルーチンを使用する手もある。


対話形式の入力を得る場合には常にプロンプトを表示する


これを実装するのはかなり難しいので、以下のCPANモジュールを使用する。



use IO::Interactive qw( is_interactive );

if (is_interactive()) {
print $PROMPT;
}


プロンプトの表示にはIO::Promptを使用する


対話形式で入力を取得したい場合、IO::Promptを使用すると簡単。
これは質問、回答、検証という一連の処理を抽象化できる。



#通常はコレ
my $cmd = $EMPTY_STR;

CMD:
while ($cmd !~ $QUIT) {
if (is_interactive()) {
print get_prompt_str();
}

$cmd = <>;
last CMD if not defined $cmd;

chomp $cmd;
execute($cmd)
or carp "Unkown command: $cmd";
}

#IO::Promptを使用した場合
while ( my $cmd = prompt(get_prompt_str(), -fail_if => $QUIT) ) {
execute($cmd) or carp "Unkown command: $cmd";
}



$cmd変数のスコープがループ内になり、改行も自動的にchompされる。


対話モード以外の処理がしばらく続く場合は、進行状況をユーザに伝える


これには、Smart::Commentsを使用する。



use Smart::Comments;

for my $possible_confg ( @CONFIG_PATH ) { ### 初期化...終了
init_from($possible_config);
}

my $connection;
TRY:
for my $try (1..$MAX_TRIES) { ### サーバへの接続...終了
$connection = connect_to($REMOTE_SERVER, {timeout=>$TIMEOUT});
last TRY if $connection;
}

croak "Can't contact server ($REMOTE_SERVER)"
if not $connection;

#ここから対話モード開始



このモジュールはwhile,forと同じ行に、###マークが付いたコメントを配置することで、自動プログレスインジケータ(処理中であることを表す表示)を生成することができる。
詳細は18.13節「半自動デバック」参照。


自動フラッシュを設定する場合は、生のselectを使用しない


フラッシュは、バッファにたまったデータを吐き出すこと。
よく使われる下記のコードは保守しにくい。



select ((select($fh), $|=1)[0]);



selectはファイルハンドルをprint文のデフォルトの出力先として使用する。グローバルで。

レキシカルファイルハンドルを使用する場合には、IO::Handleを使用することで簡単に書ける。



use IO::Handle;

$fh->autoflush();
*STDOUT->autoflush();#パッケージスコープの標準ファイルハンドルでも使用可能





Perl best practices[Perlベストプラクティス] 10章 IO 10.1~10.10



ファイルハンドルに裸のワードを使用しない



open FILE '<', $filename
or qroak "Can't open '$filename': $OS_ERROR";



 このFILEはシンボルテーブルのエントリ(グロブ)に格納される。


間接的はファイルハンドルを使用する


 レキシカル変数にファイルハンドルへの参照を代入することにより、名前の競合が防げるほか、スコープを外れたら自動的にファイルハンドルを自動的に閉じるというメリットもある。


ファイルハンドルはすぐにローカル化する。


 理由はいわずもがな。注意点としてはローカル化したあとにネストされたサブルーチンがあると、ローカル化された変数が継続して有効だと言う点。


IO::Fileモジュールを使用するか、引数3つのopenを使用する


 まれだが、'>temp.log<'などというファイル名が指定されると、入出力モードにバグが入り易い



open my $active, '<', $ACTIVE_LOG or croak "Can't open $ACTIVE_LOG: $OS_ERROR";

my $active = IO::File->new($ACTIVE_LOG, '<')
or croak "Can't open $ACTIVE_LOG: $OS_ERROR";



結果をチェックせずにファイルへのopen,close,printを行わないこと


 必ず毎回チェックを行うこと。
 しかし、毎回毎行チェックすると構文が汚れるので、13.2節を参考にFatalモジュールを使う。そうすると、組み込み関数で失敗すると例外が送出されるので、openする度に結果をチェックするコードを書かなくてよくなる。


for(<>)ではなくwhile(<>)を使用する


 forの反復リストはリストコンテキストなので、<>演算子がリストコンテキストで呼び出される。すると、可能な限りすべての行を読み取ってから、一時的なリストを作成する。これはすべての行を読み込み終わるまで、forループが開始しないことを表す。
 そのため、対話モードで使用することはできないし、メモリも入力に対して200%ほど必要になり、メモリ割り当てやスワッピングで処理速度が話にならないほど低下する恐れもある。
 入力を読み取る際はwhileを使用すること。ただし範囲(2..1000)はfor文を使用しても問題ない。


ファイルの丸呑みではなく行ベースのIOを使用する


 ファイル内容を丸呑みすると、1行ずつ処理する場合に比べて、時間がかかるし、堅牢性とスケーラビリティに欠ける。丸呑みすることの利点は、ファイルの内容が不安定な場合や、処理が複数行に渡る場合など。



while (my $line = <>) {
$line =~ s/$EXPLETIVE/[DELETED]/gxms;
print $line;
}



丸呑み



my $code = do { local $/; <$in> };


 $/は入力のセパレータ文字を指定するが、local化は初期値にundefが入るため、セパレータがundef、つまり存在しないので、行で区切られずに、ファイル内容を丸呑みする。
 また、$/の変更の影響を最小限に抑えるためにdoブロックで囲んでおく。

perl6では次のように自然に書くこともできる



use Perl6::Slurp;

my $text = slurp $file_handle;


これはスカラーコンテキストではファイル全体を読み込み、リストコンテキストでは1行ずつのリストとしてファイル全体を読み込む。
また、セパレータに段落や、正規表現を組み込むことも可能になる。





2010年9月7日火曜日

Perl best practices[Perlベストプラクティス] 9章 サブルーチン



サブルーチンは括弧付き、かつ&無しで呼び出す



  • サブルーチンは現在の名前空間で定義されている場合は括弧無しで呼び出せる。

    • これは覚えておくと良い。裸の文字列がサブルーチンとして認識されうるということ!

    • 括弧がないと、組み込み関数と区別しにくくなる

    • &はビット演算子でもあるので、構文によっては紛らわしいことになる。

    • サブルーチンが引数を取らない場合も括弧をつける。そうすれば型ではないことがはっきりする。obj()->update($status);





組み込み関数と同じ名前を使用しない


 サブルーチンと組み込み関数の名前が衝突すると、どちらが優先されるかは、その組み込み関数による。


常に@_を展開する



  • $_[1],$_[2]は何を表しているのか謎

  • @_はエイリアスなので、レキシカル変数にコピーしておくことで、元の変数をうっかり変更しない

  • 1行のリストで展開するのが良いが、例外として、一つ以上の引数を検証する必要がある場合や、インラインコメントを付ける場合は、shiftを使用する。

  • 引数の検証にサブルーチンを使う場合は、3章で紹介したように内部処理専用のサブルーチンを表す名前をつける。(頭にアンダーバー)

    • エラーの送出は、検証用のサブルーチンを呼び出した側の名前空間になるようにすること。(どこで呼び出されてエラーになったのかが知りたい情報)

    • 頻繁に呼び出す場合は、効率のために検証をサブルーチンではなく、インライン展開する。





引数が3つ以上ある場合は、ハッシュを利用する


 ハッシュで変数名をキーとして、サブルーチンの引数に渡すと、引数の順番を気にしなくてよくなる。
$line = padded({ text=>$line, cols=>20, centered=>1, filler=>$SPACE });
 これを、{}を省略して、ハッシュではなく、名前と値のペアとして渡すと、コンパイル時にエラーが出なくなるので、注意。
 通常使用する名前のない引数と、今回の名前付き引数を混在させると、オプション用の引数を明確に分離することができる。



$line = padded( $line, {cols=>20, centerd=>1, filler=>$SPACE} );

sub @added {
my ($text, $arg_ref) = @_;
#以下略
}




引数が欠けているかの検証は、引数の数や、定義されているかで判断する



  • 論理演算子を用いた検証は一般に誤り

    • 引数の文字が'0'であったり、テキストが空文字だった場合、例外が送出される。



  • 引数が定義されているかで評価

    • if any {!defined $_} $text, $cols, $filler;



  • undefが引数として渡される場合

    • definedが使えないので、引数の数が予めわかっている場合は数をチェックする



  • 引数の一部がオプションの場合

    • オプション引数がハッシュであると、オプションがいくらあっても引数の数としては、1個であるため、チェックがし易い。オプションが完全に省略されていた場合は、カウントされない。





引数のデフォルト値



  • ||や||=を使用しない。

  • 初期化のコードと、実際に値を使用するコードは分離すること。

  • デフォルト値の数が多い場合はテーブルを利用する。



Readonly my %PAD_DEFAULTS => (
cols => 78,
cnterd => 0,
filler => $SPACE,
);

sub padded {
my ($text, $arg_ref) = @_;

my %arg = ref $arg_ref eq 'HASH' ? (%PAD_DEFAULTS, %{arg_ref})
;
#略



 このコードのミソは、(%PAD_DEFAULTS, %{arg_ref}) にあって、%PAD_DEFAULTSが%{arg_ref}の前にあるため、まずはデフォルト値で%argが初期化され、次に%{arg_ref}で上書きされる。(ハッシュなのでキーアクセスだから)
 このため、arg_refで指定されなかった値にはすでにデフォルト値がセットされる。


スカラーをreturnするときは、常にreturn scalarを使用する。



  • returnのコンテキストは呼び出し側のコンテキストで決まる


 例えば、以下のように、要素数が知りたいために、スカラーコンテキストでhow_many_defined()を呼び出した際に、誰かが、以下のようにした場合、リストコンテキストとなり、リストの最初の要素が返されてしまう。



my ($found) = how_many_defined(@raw_samples);



戻り値はユーザがマニュアルを読む前に予想するものにする


 リストなどと異なり、スカラーは複数の種類(BOOLや数、文字列、参照など)が存在する。この複数ある種類の中から、1つだけを返すことが理想となる。このため、ユーザがサブルーチン名等を見たときに、どんな値がreturnされるのをユーザが期待しているかを考える必要がある。
 一人で考えるのは限界があるが、まずは、サブルーチンのカテゴリーをもとに考える。カテゴリーは同種(Cでいう配列)、異種(Cでいう構造体)、反復(readline関数など)。



  • 同種

    • 同種は各要素が同じ重要度であり、スカラーコンテキストで要求された場合、通常、要素数が要求されているはず。



  • 異種

    • 重要度が高い要素が含まれていることが多く、スカラーコンテキストではその要素が返されることが期待されているはず。だが、すべて重要だと期待されることもある。(localtimeやgettimeなど)



  • 反復

    • readlineなどに代表されるように、スカラーコンテキストでは、1回の反復の結果を返すことが要求されているはず。





ユーザが期待する戻り値を特定できない場合は、複数のreturnを準備する


Contextual::Returnを使用する


use Contextual::Return;

sub get_server_status {
my ($server_ID) = @_;

my %server_data
= _ascertain_server_status($server_ID);

return (
LIST { @server_data{ qw( name uptime load users )}; }
BOOL { $server_data{uptime} > 0; }
NUM { $server_data{load}; }
STR { "$server_data{name}: $server_data{uptime}"; }
HASHREF { \%server_data; }
);
}



 これほど多くの選択肢を準備する必要がないとしても、Contextual::Returnを使用することで、コードがすっきりするし、今後追加する際の保守性も向上する。wantarrayなどを使用すると、ifの分岐でコードが散乱するが、このモジュールだと、return文の中で表のように記述できるので、各コンテキスト間での違いを際立たせることができる。


サブルーチンプロトタイプを使用しない(一般に誤解を与える)


 sub clip_to_range($$@){....}の($$@)は、一つ目の引数がスカラーであることを要求しているように見えるがそうではない。実際は一つ目の引数がスカラーコンテキストで評価されることをperlに要求している。
つまり、clip_to_range(@range,@samples)のような呼び出しをした場合、サブルーチン側で@rangeを元に、2つのスカラー値に分割されたり、スカラーではないことを理由にエラーになるのではなく、配列の要素数が得られることになる。
 また、この場合($$@)の@には引数が与えられないことになるが、これもコンパイルエラーにはならない。「空のリスト」もリストだから。
 このように、プロトタイプを使用してもコードの堅牢性は大して向上しない。


常に明示的なreturnを使用する


 とにかくreturnは最後につけること。そうしなければ、「最後に評価された式」の結果が帰ってくるが、それはperlコンパイラが密かに変換した式で行われたかもしれない。そうなると、帰り値を元に自分で書いたコードを読んでも理解できない。


returnは自動で呼び出し元のコンテキストに応じたfalseを返すので余計な指定はしない


 return undef;などと指定すると失敗する可能性がある。それは呼び出し元がリストコンテキストで呼び出された場合。この場合、リストの要素としてundefが格納される。もし次に、if文でスカラーコンテキストで評価されるとしたら、リストの要素数は1なので、常にifの評価はtrueになる。





2010年9月6日月曜日

Perl best practices[Perlベストプラクティス] 7章 ドキュメント



ディレクティブ≒コマンド C言語の#includeや#defineもディレクティブ


ユーザドキュメントと、テクニカルドキュメントを区別する



  • ユーザドキュメント

    • エンドユーザ向け

    • ユーザはperldocを使って説明を読むだろう

    • だからドキュメントはコードのPODのパブリックセクション(=head1,=head2,=over,=item,=backセクション)に書く



  • テクニカルドキュメント

    • 開発者、保守担当向け

    • PODも読むが、ソースコードを読む時間のほうが長いだろう

    • だからドキュメントは非パブリックセクションに追いやる





また、内容を区別すること。


PODテンプレートを用いる


まったくの白紙状態からドキュメントを書くのは、心理的にかなりキツい。
テンプレを用意して、それを埋める作業にすることで、モチベを維持する。

Emacsでの設定は、



;;load an application template in a new unattached buffer...
(defun applivation-template-pi ()
"Inserts the standard Perl application template" ; For help and info.
(interactive "*")
(switch-to-buffer "application-template-pl"))
;; Set to a specific key combination...
(global-set-key "\C-ca" 'application-template-pl)



拡張テンプレート


前述テンプレートは最小限のものであり、場合により、拡張する。
たとえば、よくある質問と回答履歴とか。


ユーザドキュメントはソースファイルに添付する


ドキュメントを別に管理するのは面倒


近接性



  • ドキュメントは1ヶ所にまとめること。

    • 対応するコードに書くと、コードを汚すことになる。

    • なぜか対応するコードの近くにあるコメント(ドキュメント)は、更新されない傾向があるらしい。





PODはファイルの最後に記述する



  • コードを読もうとして、先頭に数百行のドキュメントがあるとゲンナリする。

  • __END__ラベルのあとに配置して、コンパイル時に調査されないようにする。

  • __DATA__セクションを使用する場合は、ドキュメントを=pod/=cutで囲み、__DATA__ラベルの直前に配置する。



テクニカルドキュメントは適切に再分割する


構成ごとに別々の.podファイルまたはテキストドキュメントを使用する。
構成例は以下。



  • テクニカルドキュメント構成

    • 外部資料

    • 設計書

    • データディクショナリ

    • アルゴリズムの概要

    • 変更ログ





ユーザドキュメントの「See Also(参照)」セクションでこれらのファイルの存在を明記すること。
内部資料、実装の説明、保守に関する注意事項などは、以降の節で説明する。


コメント



  • チームに適したコメントテンプレートを作成する。

  • サブルーチンやメソッドの内部資料を作成するために、箇条書きできるテンプレを用意すること。

    • 一貫性のあるコメントは読み易いし、ここでも「空白を埋めるだけ」効果で心理的なしきい値が低くなる。





アルゴリズムのドキュメント



  • アルゴリズムのドキュメントは、ソースコード内の一行コメントとして、インラインコメントする。


※これはソースの上にかく(次節の「補足用ドキュメントはソースと同じ行の行末に添える」)



# 元の配列をキャッシュする
$raw .= $var_name;



  • それらをつなげて読めばアルゴリズムが推測できるようになるのが理想。

  • コメントが1行で収まらない場合は、コードを見直す必要がある。



補足のためのドキュメント


コードは読めばわかるように書いてあるはずなので、そのヒントとなるドキュメントは必要ないが、専門用語が含まれている場合などは補足しておく必要がある。
この場合は、ソース行末に続けてインラインコメントを書く。



my $QFETM_func_ref; # 量子電界効果伝導モード関数を格納する



コメントがソース行末に収まらない場合は、「表にでない」PODを使用する。後の節で説明。


防御的ドキュメント



  • 迷ったものにはコメントをつける。

  • 未来の自分も、他人も後から苦労するのが目にみえている。

    • 例えば組み込み関数octは引数を8進数にした結果を返すのではなく、実際は、8進数を10進数に変換する。

    • マニュアルで調べなければいけないもの、構文や意味を理解するのに5秒以上かかるものにはインラインコメントをつける。





@option = map +{ $_ => 1}, @flags; # マップブロックではなく
# ハッシュコンストラクタ



コメントを書くよりもコードを書き直すべきか考える



  • コードにコメントを残す必要がある場合は「少なからず」コードを変更する必要性があることを意味する。


先ほどのmapを変更すると以下のようになる。



@options = map { {$_ => 1} } @flags; #ハッシュ参照を返すマップブロック



この例はコメントは要らないかもしれないが、書く場合は事実を端的に書く。


長いテクニカルドキュメントは「表にでない」PODセクションを使用する


 ソースコード内に長い内部資料を埋め込むには、=forと=begin/endを使う。
 以下、=forと=begin/endの違いと使い分け



  • 共通項目

    • コンパイル時に無視され、PODフォーマッタに処理されても目に見える出力がされない。

    • =cutを指定する必要がある。

      • コンパイラをドキュメントスキップモードからPerlコードコンパイルモードに戻すため。



    • フォーマット名を指定する。例えば=for html,=for groffなど。

      • フォーマット名がPOD標準の名前でない場合は、作成した文書がソースコード以外から参照できなくなるかもしれないが、あえてそうする場合は、フォーマット名の頭を大文字にして、最後に:を付ける。これは、設計や実装が特殊であることを示す特殊な目印になる。



    • 表にでないテクニカルドキュメントはできるだけコードの近くに配置する。

    • 内部で使用するものであり、PODフォーマッタの対象ではないので、PODマークアップを使用しない。




  • =for形式を使用する場合

    • 基本的に=for形式を使う。

    • 1段落で収める

    • =forは最後に空行が終了条件である点だけが=begin/endと異なる。




  • =begin/end形式を使用する場合

    • 複数段落

    • サンプルコードの埋め込みが必要





ドキュメントを見直す



  • ユーザや保守担当者との意志疎通として書かれるドキュメントは、あいまいであってはならない。

  • ドキュメントを見直す場合は、書き上げたPODを読み返すのではなく、レンダリングされたものを読む

    • PODをperldocを使用してテキストで読む

    • pod2htmlでHTMLで変換

    • pod2latexで変換




適切な表示ツールを使って最後まで読むのがいい。



  • コードを良く知らない人にチェックしてもらう。





Perl best practices[Perlベストプラクティス] 8章 組み込み関数



ソート中にソートキーを再計算しない



  • perlのソートはマージソートなので、sortの度にsortブロックが¥mathrm{O}(N¥log N)回呼び出される。

以下に記載されていた方法を紹介するが、それぞれ得手不得手があるので、使うときにベンチマークをすることが重要。
(8.8節では、ソートを最適化するための方法をもう一つ紹介されている。)

一度計算した結果をハッシュに登録しておく

Orcishと呼ばれる手法(キャッシュをOrするが名前の由来)

#スクリプトのSHA-512によるソート
use Digest::SHA qw( sha512 );

@sorted_scripts
= do {
my %sha512_of;

#ハッシュを参照して、ハッシュに登録されていなければ、$sha512($a)を計算して、結果をハッシュに登録
sort { ($sha512_of{$a} ||= ($sha512($a))
cmp
($sha512_of{$b} ||= ($sha512($b))
}
@scripts;
};



他でも計算結果を使い回す場合は%sha512_of宣言をソートの外に出して、スコープを変える。またその場合はdoブロックは要らない。

予めダイジェストを計算しておき、ソートする

この場合、ハッシュスライスが使える!

@sha512_of{@script} = map { sha512($_) } @scripts;



ただし、ソートから例外が出されて、ソートが途中で終わる可能性があるときは、予め計算したSHA-512ダイジェストが無駄になる。

schwartzian変換

このような方法もあるが、若干低速らしい。

#sortした値自体が欲しくないときに使う。
@sorted_scripts
= map  { $_->[0] }               #スクリプトを抽出
sort { $a->[1] cmp $b->[1] }   #ダイジェストでソート
map  { [$_, sha512($_)] }      #予め対応するスクリプトとダイジェストの結果を格納する。
@scripts;



サブルーチンの結果を記憶する

一番分かりやすいのは、サブルーチンの結果を記憶すること。(ただし前述のものよりは若干低速。schwartzianとはどうだろう・・・)
どういうことかと言うと、一度サブルーチンを呼び出すと、そのときの引数と結果を記憶していて、同じ引数で再度サブルーチンが呼び出された場合は、記憶している結果をそのまま使用するというもの。
PerlではMemoizeモジュールで簡単に実現できる。

use Digest::SHA qw( sha512 );
#SHA-512ダイジェスト関数の自己キャッシュ
use Memoize;
memoize('sha512');

#自動的にキャッシュされたSHA-512ダイジェストによるソート
@sorted_scripts = sort { sha512($a) cmp cha512($b) } @scripts;



リストの反転にはreverseを使う


分かりやすいし、早い。
@sorted_results = sort {$b cmp $ a} @unsorted_results;
より
@sorted_results = reverse sort @unsorted_results;
とした方が早いことすらある。


また、forループを逆順にループするときに使用すると、簡潔に書ける。

for my $remaining (reverse $MIN..$MAX) {
print "T minus $remaining, and counting...\n";
sleep $INTERVAL;
}



これをCスタイルループのデクリメントで実現しようとすると、前にも紹介した
Cスタイルの弱点が現れてしまう。
1、比較演算子の選択を誤る(<と<=を間違えるとか)
2、反復変数の更新をしくじる

スカラーの反転にもreverse


スカラーにreverseを適用すると文字列の文字を並べかえることもできる。
ただし、スカラーであることを明記すること。

my  $visible_email_address = scalar reverse $actual_email_address;


明記しないと、以下のようにサブルーチンの引数として使用した場合、
リストに作用して、ひとつしかないリスト要素を反転してしまう。

add_email_addr(reverse $email_address);#add_email_address(scalar reverse $email_address)とする。



固定幅のデータにはunpackを使用する


X123-S000001324700000199のような固定長のテキストデータの塊が使われていることがよくある。
ここから、特定文字列を取得することを考えると、


  1. substrはコードが汚くなるし、処理が遅い。

  2. 正規表現を使っても処理が遅い。




組み込み関数unpackはこのような時のために最適化された関数。'A'指定子を使用することで、文字列から文字を取り出すことができる。

Readonly my $RECORD_LAYOUT => 'A6 A10 A8'; #6文字 10文字 8文字の並びで指定

while (my $record = <$sales_data>) {
my ($ident, $sales, $price)#文字列右から6文字,10文字,8文字を、リストに格納
= unpack $RECORD_LAYOUT, $record;

#それぞれハッシュ登録してpush
push @sales, {
ident => translate_ID($ident),
sales => $sales * 1000,
price => $price,
};
}


データがX123-S 0000013247 00000199のように、何かで区切られている場合は@指定子を使用して、
文字列の何文字目がそれぞれのフィールドの先頭なのかを教える。

Readonly my $RECORD_LAYOUT
=> '@0 A6 @8 A10 @20 A8'
#@は昇順である必要はないので、好きな順序で文字列を取得することができる。



可変幅のデータにはsplitを使用


一般的に使用されていないが、splitの第3引数の使用を推奨する。
この第3引数はsplitから返されるフィールドの最大数を指定する。
予め返されるフィールドの数がわかっている場合は、数を指定することで、無駄な分割を防ぐことができる。
(例えば、splitの戻り値を受け取る変数が3つしかない場合、それ以上分割する必要はない)

ただし、注意するべきことは、
指定する第3引数は、取得したいフィールドの数+1を指定すること。
レコードから、「最初の3つ」のフィールドを取得するためには、レコードを4つに分割する必要がある。

複雑な可変幅のデータ


Text::CSV_XS、Text::CSV::Simple,Text::CSV

単純な文字列の並びならsplitで良いが、フォーマットルールは気まぐれに変化する。
これに対応するのは苦痛なので、モジュールを使用する。

Text::CSV_XSでは、フィールドセパレータ、エスケープ文字、フィールド引用符デリミタとして
使用される文字を指定し、フィールドから文字列を取得する。
Text::CSV::Simpleは、Text::CSV_XSのラッパーで、よりシンプルにモジュールを使用できる。

use Text::CSV::Simple;

my $csv_format
= Text::CSV::Simple->new({
sep_char    => q{,}, #フィールドセパレータ
escape_char => q{\\},#バックスラッシュのついた文字は常にデータ
quote_char  => q{"}, #フィールドは2重引用符で囲むことが可能
});

$csv_format->field_map( qw( ident sales price) ); #フィールドの順に名前を指定

#一度の呼び出しで、ファイル全体を読み込みハッシュリストに変換
#その後forでハッシュを使用してフィールド値を取得している。
for my $record_ref ($csv_format->read_file($sales_data)){
push @sales, {
ident => translate_ID($record_ref->{ident}),
sales => $record_ref->{sales} * 1000,
price => $record_ref->{price},
};
}




Text::CSVはピュアPerlで実装されているので、コンパイル済みのモジュールを使用できない環境なら
こちらを使用する。

文字列の評価


eval


  1. 処理に時間がかかる

  2. コンパイル時に警告が生成されない




evalを使用したい状況の代表は、
「ユーザから渡された式に基づいて、新しいサブルーチンを作成する」こと。

匿名クロージャによるサブルーチンの作成

作成予定のサブルーチンの名前と、作成するサブルーチンの機能を選択するオプションとなる引数をサブルーチン作成用のサブルーチンに渡して、
そこで、オプションで指定した匿名サブルーチンを作成する。
そして、Sub::Installerを用いて、名前と匿名サブルーチンを呼び出し元の名前空間にインストールする。
これは構文に誤りがあればコンパイルエラーになる。

ソートの自動化


Sort::Makerによるソートルーチンの作成
いろいろなソート方法を実現できる。
引数を降順でソートするようなよく使う単純なソートも名前付き引数で指定できるので、
積極的に使うようにする。

部分文字列


以下の組み込み関数に値を代入するという奇妙な構文は、
substrで指定した部分文字列を右辺の文字列で置換する機能をもつ。
だた、これは理解しにくく、かつ処理が遅い。

substr($addr, $country_pos, $COUNTRY_LEN)
= $country_name{$country_code};



perl5.6.1からはsubstrに第4引数が指定できるようになり、この引数で部分文字列の置換ができるようになった。
こちらの方が構文も自然で高速であるので、常にこちらを使用する。

ハッシュ値


ハッシュのvaluesはエイリアスなので、値を直接利用することができる。
ただし、perl5.6以前はできないので、インデックスキーを指定してアクセスする必要がある。

グロブ


入力演算(IOなど)ではない場合は、<>ではなく、グロブであることを明示するglob()を使用すること

my @files = <*.pl>;
はreadline処理と勘違いされやすいが、グロブである。


山括弧<>が入力演算の働きをするのは


  1. 空である場合<>

  2. 裸のワードだけ<DATA>

  3. 単純なスカラー変数<$input_file>



山括弧にそれ以外のものが含まれる場合は、ディレクトリ参照が実行される。
つまり、拡張子が*.plのファイル名のリストが返されることになる。

また、最悪なパターンとして、'*.pl'が定数として定義されてしまった場合、
スカラー変数を<>で囲むことになるので、グロブではなくなってしまう。


常に、
Readline my $FILE_PATTERN => '*.pl';
my @files = glob($FILE_PATTERN);
のようにする。

スリープ


組み込み関数sleepは整数の値しか対応していないが、selectの副作用を使ったスリープで1秒未満のスリープを実現しないこと。代わりにTime::HiRes::usleep関数を使用すること。
Time::HiRes::usleep関数を使用できない場合に、selectを使用する場合はサブルーチンでカプセル化すること。
ただし、サブルーチン呼び出しのオーバヘッドでわずかだか正確さに影響がでる。(気にしなくていい程度らしいが)

mapとgrep


mapとgrepの式は、常にブロック{}で囲むこと。そうしないと、後に続くリストと区別がつきにくくなる。

ユーティリティ 組み込みでない関数を使用する


次の3つのモジュールには便利な関数が複数存在する。


  1. Scalar::Util

  2. List::Util

  3. List::MoreUtils



詳しくは紹介しないけれど、first,reduce,apply,uniqなどがよく使いそう。