hitode909の日記

趣味はマリンスポーツですの日記です

設定のクラスを作るとすっきりしそう

設定のテストを書くとよいって言ってる人がいた.

テストされてるのはよいと思う.名前のついてないデータ構造をがんばってテストするよりは,設定のクラスを作るとすっきりしそうと思った.
こういう構造のHash,として見るよりかは,設定クラスのインスタンスとして見るほうがイメージしやすい.


個々のブログの設定のURLはユニークであるというのを,どこかのクラスの責任にする.BlogConfigRepositoryというクラスのインスタンスが,設定の集合を持ってるとか.

like exception {
    BlogConfigRepository->new([
        {
            "url" : "http://blog.example.com/",
            "permission" : "public",
            "members" : [ "example-user" ],
        },
        {
            "url" : "http://blog.example.com/",
            "permission" : "public",
            "members" : [ "example-user" ],
        },
    ]);
}, qw/url duplicated/;

クラス作っておくと,データと対応した操作も作れるので,指定したURLの設定を取ってくるメソッドなども作れる.$config->{$url}とか書くより,find($url)って書けるほうが読みやすい.

my $repository = BlogConfigRepository->new([
    {
        "url" : "http://blog1.example.com/",
        "permission" : "public",
        "members" : [ "example-user" ],
    },
    {
        "url" : "http://blog2.example.com/",
        "permission" : "public",
        "members" : [ "example-user" ],
    },
]);

isa_ok $repository->find("http://blog1.example.com/"), 'BlogConfig';
is $repository->find("http://blog1.example.com/")->url, 'http://blog1.example.com/';
ok ! $repository->find("http://undefined.example.com/");

設定1個のクラスも作っておくと,個々の設定のバリデーションさせることができる.

like exception {
    BlogConfig->new({ permission => 'public', members => ['example-user']});
}, qw/url required/;

like exception {
    BlogConfig->new({ url => 'http://blog.example.com/]', members => ['example-user']});
}, qw/permission required/;

like exception {
    BlogConfig->new({ url => 'http://blog.example.com/]', permission => 'public'});
}, qw/members required/;

like exception {
    BlogConfig->new({ url => 'http://blog.example.com/]', permission => 'undefined', members => ['example-user']});
}, qw/invalid permission/;


BlogConfigはハードコードされたHashを読み込むだけじゃなくて,アプリケーション内で動的に作るかもしれないので,permissionはこれかこれに限るとか,インスタンス作る前に,妥当なpermissionか知る手段があると便利そう.

cmp_deeply BlogConfig->valid_permissions, ['public', 'private'];
ok BlogConfig->is_valid_permission('public');
ok BlogConfig->is_valid_permission('private');
ok ! BlogConfig->is_valid_permission('invalid);


個々の設定を表すクラスに,publicかどうかとか,この人は編集できますかとか,アプリケーション的に必要になりそうなロジックを置ける.

my $blog_config = BlogConfig->new({
    "url" : "http://blog.example.com/",
    "permission" : "public",
    "members" : [ "example-user" ],
});

is $blog_config->url, "http://blog.example.com/", 'BlogConfigはurlを持つ';
ok $blog_config->is_public, 'publicである';
ok ! $blog_config->is_private, 'privateではない';
cmp_deeply $blog_config->members, ['example-user'], 'メンバーを取得できる';
ok $blog_config->can_be_edited_by('example-user'), 'メンバーは編集できる';
ok ! $blog_config->can_be_edited_by('another-user'), 'メンバーは編集できる';


こんな感じにすると設定のデータの仕様とその操作が一体化して便利だと思った.
コードの例は架空のブログの設定であり,実在するブログの設定とは関係ありません.


書き忘れてたので追記.便利とかすっきりする以上のメリットもあって,設定に対応するクラスを作ることで,データの整合性についての知識が,上のエントリではテストだけに書かれていたのが,このエントリのコードではアプリケーション本体のドメインに移ったのがよい.ドメイン層のロジックが充実するのは一般的にめでたいこととされている.外から釘を打ちつけて固定してたところに柱が入ったような安定感がある.