Hatena::Groupdann

dann's blog このページをアンテナに追加 RSSフィード

Fork me on GitHub

2008-06-05

現状のController拡張のベストプラクティスから次のプラクティスを考える

| 現状のController拡張のベストプラクティスから次のプラクティスを考える - dann's blog を含むブックマーク はてなブックマーク - 現状のController拡張のベストプラクティスから次のプラクティスを考える - dann's blog 現状のController拡張のベストプラクティスから次のプラクティスを考える - dann's blog のブックマークコメント

Controllerの拡張の現状のベストプラクティスということで、

http://search.cpan.org/src/JCAMACHO/Catalyst-Controller-FormBuilder-0.04/lib/Catalyst/Controller/FormBuilder.pm

があるよ!というのをcharsbarさんに教えてもらいました。

メソッドバッティング対策は、configでメソッド名を渡せるようにしてるみたいですね。

前のエントリーで書いたように、MooseのRoleベースでするともっと綺麗な形にできるんじゃないかなぁという気はします。ControllerにRoleをmixinする形にしたいなぁと。できることはかわらないとは思いますが、Catalyst::Controller::Hogeを多重継承することになるということの違和感から解放することはできそうです。

現状のmoose branchだとCatalyst::Controllerを継承して、そこでRoleをmixinしてやればいけそうな気もしますが、そうではなくてmixinするRoleを設定ファイルに書く程度になったらいいんじゃないかなぁという気はします。

ベースのControllerで、以下みたいな感じでかけるといいんじゃないかなぁと。基本的には使う人にはMooseが生で見えない形のほうがいいんじゃないかなぁというだけで、withでRoleをconsumeするのと変わらないわけですが。

Catalyst::Controller::Plugin::FormBuilderのパッケージにして、それをMooseのRoleにするというイメージでいます。そのRoleをpluginという形で見せてconsumeするというイメージです。

package MyApp::BaseController;
BEGIN {
  extends 'Catalyst::Controller';
}
__PACKAGE__->load_plugins(qw/FormBuilder/);

ユーザーにはプラグインという形で拡張を見せる仕組みのほうがいいんじゃないかと思うんですね。ただ、ちょっと世間一般で言うプラグインではないわけですが、適切なスコープに対する機能拡張という目的を綺麗な形で実現できるんじゃないかなぁとは思います。

Catalystプラグインが乱立した理由について

Catalystプラグインが乱立した理由について - dann's blog を含むブックマーク はてなブックマーク - Catalystプラグインが乱立した理由について - dann's blog Catalystプラグインが乱立した理由について - dann's blog のブックマークコメント

Catalyst::Plugin::* になにを置いていいのか、というのがわかりにくかったというのはある。今は方針があきらかになってるからいいけど。

View にたいするプラグインも、Controller にたいするプラグインも、Request も Engine も Context も。ぜんぶ Catalyst::Plugin::* においてある。だから混乱したんじゃないでしょうか。

http://d.hatena.ne.jp/tokuhirom/20080605/1212624726

これは本当にそうだなぁと思います。

フックポイントにhookするようないわゆるプラグインもあれば、コンテキストにメソッドはやすだけのものもあればもあったりと、色々なものがプラグインという形でごちゃまぜになっています。

一般的には、フレームワーク側がプラグインを制御するという、いわゆる制御の逆転がおきる形のものをプラグインというのかなとは思っています。その点、Catalystはそのようなプラグインでないものまでもプラグインという形で提供する事にしてしまったというのが、カオス化したことの原因なんだろうなぁと思います。

利用者視点と開発者視点

 利用者視点と開発者視点 - dann's blog を含むブックマーク はてなブックマーク -  利用者視点と開発者視点 - dann's blog  利用者視点と開発者視点 - dann's blog のブックマークコメント

今回の一連のプラグインの話を見ていても、作る側の視点と使う側の視点で、大分話の視点が違っているのが印象的です。

利用者視点だと、

  • 副作用があっても、副作用のないものを作ってから使うなんてことはそもそもしたくない
  • 副作用があっても、多くのプラグインを使う訳じゃないから問題ない
  • Session, Authenticationとか含めて使っちゃいけないと思っている

作る人視点だと、

  • 継承順で動作がかわるなんて、プラグインじゃない!
  • application wideにメソッド追加しちゃうの!もっと適切なスコープあるでしょ!

などで、微妙に並行線をたどっています。

どちらの視点もわかりますが、現状では個人的には副作用を意識して使えばいいんじゃないかなとは思います。実際のところは、Plugin使うな!といっても、無い物を全部移植して使いたいと思う人はごく僅かだと思うんですよね。現状の仕組みにのっとった形だと、使うな!というよりも、代替のものを提示するというほうが多くの人にとっては有益なのかなと。

# で、ここまでだと少しつまらないので、プラグインシステム(フレームワークの拡張方法)ってどうしたらもう少し良くなったんだろういうのを考えてみるのが建設的で面白いんじゃないかと思います。

プラグインのあるべき姿を考えてみる - その2

|  プラグインのあるべき姿を考えてみる - その2 - dann's blog を含むブックマーク はてなブックマーク -  プラグインのあるべき姿を考えてみる - その2 - dann's blog  プラグインのあるべき姿を考えてみる - その2 - dann's blog のブックマークコメント

プラグインの仕組みを少し考えてみます。

フックポイントにPluginの処理を差し込んで、フレームワーク側の処理に手を加えるものは、いわゆる世間一般にプラグインと言われているものなんじゃないかと思います。これはMooseのRoleのmethod modifireは相性はよさそうです。フックポイントに対して差し込む処理と差し込む処理を実行するタイミングをコントロールできるからです。

ただ、MooseのRoleのmethod modifireのパターンは、Class::Triggerのようにフックポイントが明示されているわけではないので、プラグインとして提供する場合にはフックポイントを明示する仕組みを用意したほうがいいのかもしれないなぁとは思いました。

Catalystのリクエストライフサイクルに処理を挟み込むのは、この形があっているのではないかと思います。多分、これがエンジンのプラグインとして提供されるべき機能です。

では、一般的なプラグインの定義からは外れるけれども、フレームワークに機能を追加したいというものはどうするのがよいでしょうか。例えば、Controllerなどに機能を追加したい場合です。これは、Controllerとしてリリースするのがいいでしょうか。ここには、もう少し検討の余地があるように思います。

現状の場合で、Controllerとしてリリースした場合に、複数のCapabilityを満たすControllerのベースクラスを作りたい場合がどうなるかを考えてみます。この場合、いくつかの汎用Controllerを多重継承することになります。これだとグローバルに機能共有するわけではないので、バッティングする可能性そのものは減りますが、メソッドのバッティングは防げなそうです。これだけでは、あまり良い解とはいえなそうです。

MooseのRoleはこれに対して一つの解があります。MooseのRoleをconsumeするタイミングで、違うメソッド名をつけられるからです。これによって複数の機能拡張をマージする事で運悪くバッティングしてしまったとしても、バッティングすることを回避して機能をMixinすることができます。

単一継承+Mixinは、多重継承による複雑なクラス構成をさけるのに役立ち、さらにRoleのようにaliasを用意する事で、いわゆるCatalystでいうComponentレベルでの昨日共有を促進できる可能性があるような気がします。

要するに、Mixinベースにして、さらにMooseのRoleのようにconflictした場合の回避策(別名へのalias)を用意するという形で、機能拡張をしていける仕組みを提供するというのが一つあるんじゃないかと思いました。

もっと明示的にフレームワークとして拡張ポイントを用意するというのもあるとは思うんですが、ここら辺はどういうものがプラグインになるべき仕組みとして用意できるのかがわからないので、あれだなと。

このようなことを夢想してみました。PerlでのPluginシステムフレームワークの拡張方式にはまだまだ先がありそうなので、何がよくて、何が悪いかを考えていきたいなと思っています。

xUnitでの単体テスト

| xUnitでの単体テスト - dann's blog を含むブックマーク はてなブックマーク - xUnitでの単体テスト - dann's blog xUnitでの単体テスト - dann's blog のブックマークコメント

単体テストxUnitを使う場合、クラスに対して1つのテストクラスを作るのが一般的です。例えば、Dogクラスがあれば、Dog::Testというクラスを作り、Dogの振る舞いに対してテストを書きます。

クラスの振る舞いごとに、テストメソッドを書いていきます。テストの意図をテストメソッド名にします。そのため、テストケースがそのクラスの振る舞いの仕様を表現することになります。

ですから、クラスの仕様を確認したいときは、そのクラスに対応するパッケージのテストを見ればいいという形になります。

クラスの振る舞いに対してテストをするというのが明確になるという点が、xUnit系のテスティングフレームワークのよい点の一つだと思っていて、個人的にTest::Classがいいんじゃないかなと思っています。

Test::Classでヘルパースクリプトを作らずにテストを実行する

| Test::Classでヘルパースクリプトを作らずにテストを実行する - dann's blog を含むブックマーク はてなブックマーク - Test::Classでヘルパースクリプトを作らずにテストを実行する - dann's blog Test::Classでヘルパースクリプトを作らずにテストを実行する - dann's blog のブックマークコメント

ベースクラス

package My::TestBase;
use strict;
use warnings;

use base 'Test::Class';

INIT { Test::Class->runtests }

1;

テストクラス

package My::Dog::Test;
use base qw/My::TestBase/;
use Test::More;

sub test_bark : Test(2) {
    is 'bow', "bowbow", 'dog barks';
    is 'bowbow', "bowbow", 'dog barks twice';
}

1;

上記のようにベースクラスを用意してあげると、テストのクラスでruntestsを毎回書く必要は無くなるので、ブートストラップ用のスクリプトはいらないかなぁと。

後は、prove -lv t/lib/My/Dog/Test.pm とすればよいと。

(perl的な配置だと、prove -lv t/01-dog.t とかのほうがあってるのかな。そこらはまだよくわかってないけれど、クラスに対するテストであることがわかるのがいいかなと。)

もしくは、以下のように必ずテストパッケージの下部でruntestsを書いておいてもブートストラップ用のスクリプトは不要そうです。

package My::AnotherDog::Test;
use base qw/Test::Class/;
use Test::More;

sub test_bark : Test(2) {
    is 'bow',    "bowbow", 'dog barks';
    is 'bowbow', "bowbow", 'dog barks twice';
}

__PACKAGE__->runtests;

1;