― Web Technology and Life ―

引数の明示という観点からのPerlモジュール群

2011-06-02
Perlモジュールのバリデーターと呼ばれるジャンルのモジュールなどをバリデーションという観点ではなく、引数を明示するという観点から整理しました。

バリデーターの種類

バリデーターというものは、一般的に引数が期待される型など(整数か、文字列か、URLか、メアドか等)になっているかどうかをチェックするというものです。特に、Webアプリにおけるフォームから入力されるデータのチェックに活用されることが多いです。そういういった意味から、Perlのバリデーターモジュールはフォーム系と非フォーム系に分類することができます。

フォーム系のバリデーター等

FormValidator::Lite
CPAN本にも紹介されている強力なフォームバリデーターです。フォームバリデーターとしては、フォームの型の定義、チェック、エラーメッセージの定義、が必要ですが、やりたいであろうことのだいたいがそろっているモジュールで実装したほうがのちのちの拡張が楽でいいですね。
HTML::Shakan
ウェブでフォームを作成する際のフレームワークと言えるモジュールです。フォームの、生成、バリデーター、データの登録/更新が行えるので、ある意味最軽量のWAFかもしれません。バリデーターとしては内部でFormValidator::Liteを使用しています。そして、このブログのシステムでも大活躍です。
Validator::Custom
残念ながら使ったことがありませんので、よく知りません。
Data::FormValidator
残念ながら使ったことがありませんので、よく知りません。

非フォーム系のバリデーター、あるいは、引数を明示するということ

フォームのデータに関わらず、不特定の人間がアクセスするインターフェースに関してはバリデーションをかけてあげるのが親切というものです。そのような観点から、オブジェクト変数や、メソッドの引数にバイリデーションをかけるということが行われます。以下のモジュールは、名前空間にもその意思が現れているといえるでしょう。

一方で、Perlのような自由な書き方が可能な言語では自由に書かれてしまうあまり引数が不明確になるケースが多いです。こんなときのために、非フォーム系のバリデーターにはバリデーターとしても使える一方、引数を明示するという役割を押しているものがあります。それらを紹介する前に、もう一度、引数を明示する必要性について具体例をあげながら考えてみたいと思います。

関数における引数の明示の必要性

ドキュメントがないモジュールでは絶望的ですが、ドキュメントがあっても何が書いてあるかわからないとき、あるいは、コードはコードから語られるべきという観点からコードを読むとき、以下のようなコードを見ると非常に残念に思います。オプションのハッシュには何を入れればよいのかまったくわかりません。

残念なコード① ~引数がまったくわからない~

sub new {
    my $class = shift;
    my %args  = @_;
    my $self  = bless {%args},$class;
}

一方でハッシュを送るケースの多いオブジェクトのコンストラクションであれば以下のような書き方で非常にわかりやすくなります。デフォルトの値の設定と、リファレンスかどうかの明示ができます。

わかりやすいと思うコンストラクションコード

sub new {
    my $class = shift;

    my $self = bless {
        hoge            => 'aaaa',
        moge            => 'bbbb',
        foga            => '',
        default_plugins => [],
        databases       => [],
        @_,
    }, $class;
}

ただ、以下の「残念なコード②」ようなちょっとしたロジックをかけてオブジェクトを生成したい時には、上記の「わかりやすいと思うコンストラクションコード」は使えません。また、「残念なコード①」よりましですが、それでも、%parの中身をすべて確認するには多少の時間がかかります。

残念なコード② ~引数がちらばっていて見づらい~

sub new {
    my $class = shift;
    my %par = @_;
    my $self;
    $self->{ua} = LWP::UserAgent->new(
        agent => $par{agent} || 'Mozilla/4.0'
    ) or return;
    $self->{ua}->env_proxy if $par{env_proxy};
    $self->{ua}->proxy('http', $par{proxy}) if $par{proxy};
    $self->{ua}->timeout($par{timeout}) if $par{timeout};
    $self->{host} = $par{host} || 'toolbarqueries.google.com';
    bless($self, $class);
}

わかりやすさを追求していく中で、変数名をわかりやすくすることは大変重要です。しかし、どんなにがんばってもその変数がオブジェクトなのかどうか、配列のリファレンスなのかどうかを明示することは困難です。特に、関数の行数が大きくなり、さらに引数を利用するのが最後の方だった場合に読む側の負担は大きくなります。まぁ、わりとどうしようもないところなので。残念というかはがゆい感じですね。

残念なコード③ ~オブジェクトかどうか、Arrayrefかどうかわかりづらい~

    sub hoge {
        my ($date,$urls) = @_;

        #...

        print $date->ymd;

        for my $url (@$urls) {
            print $url;
        }
    }

仕組みを作ることが仕事だとしたら、書いたコードが効率よく引き継がれる必要がありますし、効率よくメンテナンスされる必要があります。そのためには、これらのコードの可読性にかかる問題に対応しなくてはいけません。それゆえに、引数を明示する必要があります。

Sub::ArgsやSmart::Argsを使うとこんなにわかりやすい

いかに引数を明示するとコードの可読性があがるかについて、理解を深めるために「残念なコード」たちを引数を明示するの役立つPerlモジュールSub::Args、Smart::Argsを利用してわかりやすく書きなおしてみましょう。

Sub::Argsで生まれ変わった「残念なコード①」

use Sub::Args;

sub new {
    my $class = shift;
    my $args  = args({
        hoge => 1,
        moge => 1,
        fuga => 0,
    },@_);

    my $self  = bless {%$args},$class;
}

ハッシュリファレンスの中身が一目瞭然で大変わかりやすいですね!!Sub::Argsはargsという関数をエクスポートして、ハッシュとハッシュリファレンスのキーを明示して、かつ、それらが必須かオプションか明示することが可能です。詳しくは『Sub::Argsのドキュメント』を御覧ください。

Smart::Argsで生まれ変わった「残念なコード②」

use Smart::Args;

sub new {
    args my $class,
         my $agent     => {default => 'Mozilla/4.0'},
         my $proxy     => {optional => 1},
         my $env_proxy => {optional => 1},
         my $timeout   => {optional => 1},
         my $host      => {default => 'toolbarqueries.google.com'};
    
    my $self;
    $self->{ua} = LWP::UserAgent->new(agent => $agent);
    $self->{ua}->env_proxy if $env_proxy;
    $self->{ua}->proxy('http', $proxy) if $proxy;
    $self->{ua}->timeout($timeout) if $timeout;

    $self->{host} = $host;

    bless($self, $class);
}

Smart::Argsで生まれ変わった「残念なコード③」

use Smart::Args

sub hoge {
    args my $date => {isa => 'DateTime'},
         my $urls => {isa => 'ArrayRef'};

    print $date->day;

    for my $url (@$urls) {
        print $url;
    }
}

ハッシュリファレンスのキーがスカラーでうけとれてかっこいいですね!!しかも、型やデフォルト値など変数の説明もついてスマートですね!!Smart::Argsはargsという関数をエクスポートして、ハッシュとハッシュリファレンスのキーを明示して、かつ、それらが必須かオプションか、かつ、デフォルト値、かつ、型、を明示することが可能です。詳しくは『Smart::Argsのドキュメント』を御覧ください。

いろいろある引数の明示に使えるモジュール

引数の明示という観点で改めてPerlモジュールを整理すると以下のような分類ができると思います。以下、それぞれのモジュールを「変数を明示する」という観点からピックアップしつつ解説します。

オブジェクトのインスタンス変数の明示

  • Class::Accessor(::Lite)
  • Moose/Mouse

Class::Accessor

package Foo;
use base qw(Class::Accessor);
Foo->mk_accessors(qw(name role salary));

my $mp = Foo->new({ name => "Marty", role => "JAPH" });
my $job = $mp->role;  # gets $mp->{role}
$mp->set('salary', 400000);

Class::Accessorはクラス変数のゲッター、セッターを提供するモジュールですが、「Foo->mk_accessors(qw(name role salary));」という部分に変数を明示するという役割を見出すことができます。その他詳しくは、『Class::Accessorのドキュメント』を御覧ください。ちなみに、最近は、Class::Accessor::Liteが名前空間が汚れずシンプルで早いものとしてリリースされています。

Moose/Mouse

package Point;
use Mouse; # automatically turns on strict and warnings

has 'x' => (is => 'rw', isa => 'Int');
sub clear {
    my $self = shift;
    $self->x(0);
}

Perlの次世代のオブジェクト作成システムという「A postmodern object system for Perl 5」触れ込みのMoose/Mouseも、hasで指定するクラス変数の明示は大変強力です。書き込みできるのか、型はなにか、必須か、等以上に様々な機能が盛り込まれています。詳しくは、『Mouse』のドキュメントのドキュメントを御覧ください。Mooseはおもいらしいので、みんなに嫌われておりますが、Mouseは軽量版ということでオススメされてい(?)ます。

オブジェクト/クラスのメソッド/関数の引数の明示

Class::AccessorやMoose/Mouseに関してはオブジェクト変数に対してバリデーションしたり明示することが可能ですが、メソッド/関数に関しては使えません。メソッド/関数の引数を明示したい時には、以下のモジュールが有効です。

  • Params::Validate
  • Data::Validator
  • Sub::Args
  • Smart::Args

Sub::ArgsとSmart::Argsに関しては、先に紹介しているので省略しますが、他の二つに関しては簡単に紹介してみます。

Params::Validate

use Params:Validate qw(:all);

sub bar {
  my $args = validate( @_, {
    hoge  =>  { type => SCALAR, },
    fuga  =>  { type => ARRAYREF,  optional => 1 },
  });
  # ...
}

もはや古典的なメソッドバリデーターと言えるでしょう。でも、定番なので昔のドキュメントが充実しています。今現在の0.99バージョンだとなぜかドキュメントが確認できませんが。。。あと、一般的に重い、遅いと言われています。。。まぁ、@_を先に書くあたり、ちょっと見づらいですね。詳しくは『Params::Validate-0.64のドキュメント』を御覧ください。

Data::Validator

use 5.10.0;
use Data::Validator;

# for functions
sub get {
  state $rule = Data::Validator->new(
    uri        => { isa => 'Str', xor => [qw(schema host path_query)] },
    schema     => { isa => 'Str', default => 'http' },
    host       => { isa => 'Str' },
    path_query => { isa => 'Str', default => '/' },
    method     => { isa => 'Str', default => 'GET' },
  );

  my $args = $rule->validate(@_);
  # ...
}
get( uri => 'http://example.com/' );

Mooseライクで大変見やすいですね。引数の補足情報に関しても、Mooseを知っていればわかりやすいです。ただ、引数のチェックして、いちいちnewするのはめんどいかと思います。。。以下のように、CSVのバリデートとかに使うのがいいかもと思っています。詳しくは、『Data::Validatorのドキュメント』をご覧ください。

use Text::CSV::Simple;
use Data::Validator;

sub read_csv{
    my $self = shift;

    my $parser = Text::CSV::Simple->new( { binary => 1 } );
    $parser->field_map(qw/uri schema host/);

    state $rule = Data::Validator->new(
        uri     => { isa => 'Str', xor => [qw(schema host path_query)] },
        schema  => { isa => 'Str', default => 'http' },
        host    => { isa => 'Str' },
    );

    my @csv = map { $rule->validate($_) } $parser->read_file('./a.csv') or die;

    return \@csv;
}

まとめと整理とオススメ

引数を明示するという観点からのPerlモジュールまとめ

引数を明示するという観点から以上で紹介してきたモジュールを個人的な直感でまとめると以下のようになります。

評価ポイント Class::Accessor Moose/Mouse Params::Validate Sub::Args Smart::Args Data::Validator
Perlっぽい × ×
型の指定 ×
必須の指定
デフォルト値の指定 ×
関数/メソッドで使える × ×
シンプル × × × ×
はやい ×
hashrefでうけとる ×

開発フェーズに使用するとよいと思うモジュール

メソッドの要件が曖昧になる開発フェーズでは、気軽に使えるSub::Argsを使います。ちょっと他のメソッドに投げたいというときはHashRefになっていたほうがブラックボックス的に使いやすいですし(苦笑)、簡単に必須かどうかだけでも決めておくと分かりやすいです。

運用フェーズに使用するとよいと思うモジュール

具体的な要件が決まっているケースや、テストしまくって要件がfixするリリース前フェーズでは、細かい設定もできるし、ハッシュのキーをスカラーで受けてれてシンプルになるSmart::Argsを使っています。あと、今後は、CSVのチェックにはData::Validatorを使おうかという感じです。

個人的には、見た目というより、書き方がシンプルな方がいいと思っているので(特に、行数が少なくて済む物)、Sub::ArgsとSmart::Argsが好きです。CSVチェック用にはData::Validatorも好きです。瑕疵等ありましたら、お気軽にご指摘頂ければありがたいです。

最後に

この記事のBefore⇔After的なネタは、tomitaさんのPerl Casual#4のセッションから触発されました。今後も、こんな感じのBefore⇔Afterネタがどんどん出回るとわかりやすいなーと思っています。また、この記事は、hachioji.pm#5の発表をもとに個人的に書きたいことを盛りこんで書きなおしたものです。hachioji.pm#5の感想はこちらを御覧ください。いいまとめだ的な反応をくれた皆さんはありがとうございます。発表自体は、Yokohama.pm#7で紹介されてたのに触発されてSphinxを使ったのですが、文字サイズの変更がめんどうだったり、コードのハイライトがいまいちだったり、全体にスタイルを指定するのはどうすればよいのよ?とかそもそものSphinxの使い方がいまいちよくわからないところがあり、「まぁやっぱり慣れていないツールでスライドづくりは大変だ・・・、やはりpptに回帰」ということで、Sphinxのスライドもお蔵入りして、書きたいことを書きたいだけHTMLで直描きした次第です。

Perl hachioji.pm update_at : 2011-06-02T12:09:05
hirobanex.netの更新情報の取得
 RSSリーダーで購読する   
blog comments powered by Disqus