Moose-0.92 > Moose::Manual::BestPractices

題名

Moose::Manual::BestPractices - Mooseを最大限に活用する

推薦の言葉

Mooseにはさまざまな機能がありますし、使い方も決して一通りではありません。でも、使う機能は一部にしぼって、いつもそれを使うようにした方が、みなさんのためになると思います。

もちろんどんな「ベストプラクティス」集でもそうですが、これも本当にただの意見にすぎませんので、無視していただいても結構です。

no Mooseと不変化

Mooseのクラス定義の最後にはMooseのシュガー関数を削除してクラスを不変化することをおすすめします。

  package Person;

  use Moose;

  # extends, roles, attributes, etc.

  # methods

  no Moose;

  __PACKAGE__->meta->make_immutable;

  1;

no Mooseの部分はMooseがエクスポートするキーワードを全て取り払う、単にコードをきれいにするためのものです。クラスを定義した後にこれらのキーワードは必要ありませんので、削除しておくほうがよいでしょう。 make_immutableはMooseがオブジェクト生成をはじめとした色々な動作を高速化にする処理を適用することを可能にします。ただいこの代償としてそのクラスを以後変更することはできません。 また、クラスを不変化すると、いろいろなものが高速化します(もっとも顕著なのはオブジェクトの生成です)。

Mooseを含め、それ以外のキーワードのエクスポート除去も行うさらに一般的な手法としてはnamespace::cleannamespace::autocleanがあります。

newはオーバーライドしないこと

newをオーバーライドするのは非常に悪いことです。同じことをしたいのであればBUILDメソッドやBUILDARGSメソッドを使ってください。newをオーバーライドすると、クラスを不変化したときにコンストラクタをインライン展開できなくなってしまいます。

ただし、newをオーバーライドしてもよい場合が2つあります。ひとつは、自前でMoose::Objectのサブクラス「と」Moose::Meta::Method::Constructorを用意してコンストラクタをインライン展開するようなMooseX拡張モジュールを書いている場合。2つめはMooseを使っていない親クラスをサブクラス化する場合です。

やり方を知っている人なら、このベストプラクティスを無視してよいときもご存じですよね ;)

かならずSUPER::BUILDARGSを呼ぶこと

自分のクラスでBUILDARGSメソッドをオーバーライドする場合はかならず、お行儀よくSUPER::BUILDARGSを呼んで、自分では明示的にチェックしていないケースを処理するようにしてください。

Moose::Objectが提供しているデフォルトのBUILDARGSメソッドは名前付きパラメータのリストとハッシュリファレンスをどちらも正しく処理できます。また、「ハッシュリファレンス以外の」引数がひとつだけの場合もチェックしてくれます。

できればかならずデフォルトを用意すること、そうでなければrequiredを使うこと

クラスにデフォルト値が用意されていると新しいオブジェクトを生成するのが簡単になります。デフォルトを用意できない場合はアトリビュートをrequiredにすることを検討してください。

どちらもしないと、アトリビュートが単にセットされていないだけという状態になることがあるため、オブジェクトがより複雑なものになってしまいます(みなさんや、みなさんのクラスのユーザが考慮しなければならない状態の可能性が増えてしまうためです)。

よほどのことがなければdefaultのかわりにbuilderを使うこと

ビルダーは継承可能ですし、明示的な名前がついています。とにかくこちらの方が明らかにきれいなのです。

ただし、デフォルトがリファレンスでない場合、「あるいは」デフォルトが何らかの空のリファレンスでしかない場合は、「ぜひ」デフォルトを使ってください。

また、ビルダーメソッドはプライベートにしておいてください。

lazy_buildを使うこと

遅延評価は便利ですし、初期化の順番の問題を解決してくれることも多いものです。また、まったくする必要がないかもしれない作業を遅らせるときにも使えます。遅延評価をさせたい場合は、lazy_buildを使うと、タイプ数を節約して、名前も標準化できるようになります。

クリア用のメソッドや断定用のメソッドはプライベートにすることを検討すること

「本当に」だれでもアトリビュートをクリアできるようになっている必要はあるでしょうか。おそらくないはずです。この機能をデフォルトでクラスの外から見えるようにするのはやめましょう。

断定用のメソッドの方はそれほど問題ではありませんが、わざわざ必要以上に公開APIを大きくする理由はありません。

デフォルトは読み取り専用にして、書き込み用のアクセサはプライベートにすることを検討すること

アトリビュートを可変化すると、単純にプログラムの中で考慮しなければならない複雑さが増してしまいます。状態を可変化するかわりに、クラスのユーザには必要があれば新しいオブジェクトを作るようすすめてください。

どうしてもアトリビュートを読み書き可能に「しなければならない」場合は、書き込み用のアクセサを別のプライベートメソッドにしてしまうことを検討してください。APIの幅を狭めた方がメンテナンスがしやすくなりますし、状態が可変になっているとトラブルのもとです。

そのようなアトリビュートを定義する場合は、プライベートと分かる関数名をwriterに渡します:

    has pizza => (
        is     => 'ro',
        isa    => 'Pizza',
        writer => '_pizza',
    );

サブクラスでアトリビュートの型を変える前にもう一度考え直すこと

その道の先にあるのは大混乱です。アトリビュート自身がオブジェクトの場合、少なくとも親クラスのオブジェクトの型と同じインタフェースを持っているかどうかは確認しましょう。

initializer機能は使わないこと

何を言っているのかわからない? 大丈夫です。

auto_derefのかわりにMoose::Meta::Attribute::Nativeトレートを使うこと

auto_derefはいささかやっかいな機能ですし、複雑なアトリビュートを直接外に見せるのは美しくありません。かわりにMoose::Meta::Attribute::Nativeを使って、そうする必要がある機能のみを外に見せるようなAPIを定義することを検討してください。そうすれば、ほしい機能のみを外に見せることができるようになります。

もっとも具体的なサブクラスであってもかならずinnerを呼ぶこと

augmentinnerを使う場合は、階層内でもっとも具体的なサブクラスの中でもinnerを呼ぶことをおすすめします。こうしておくと、親クラスを変更しなくてもさらにサブクラス化を進めて階層を拡張することができるようになります。

型には名前空間を付けること

型の名前には何らかの命名規則を適用してください。「MyApp::Type::Foo」のようなものがおすすめです。

あとから再利用できるようにMooseX::Typesを使って型をパッケージングするつもりがあるなら、空白やピリオドのようにPerlの識別名としては使えない文字は使わないようにしてください。

Mooseの組み込み型は直接型変換しないこと

ArrayRefのようなMooseの組み込み型に型変換を定義すると、この型を利用しているPerlインタプリタ上で動作しているすべてのアプリケーションに影響を及ぼします。

    # very naughty!
    coerce 'ArrayRef'
        => from Str
        => via { [ split /,/ ] };

だから、そのかわりにサブタイプを作って、そちらを型変換してください。

    subtype 'My::ArrayRef' => as 'ArrayRef';

    coerce 'My::ArrayRef'
        => from 'Str'
        => via { [ split /,/ ] };

クラス名を直接型変換しないこと

Mooseの組み込み型の場合とまったく同じで、クラス型もインタプリタ全体に影響を及ぼすグローバルなものなので、クラス名に型変換を追加してしまうと、よそで不思議な副作用を引き起こすことがあります。

    # also very naughty!
    coerce 'HTTP::Headers'
        => from 'HashRef'
        => via { HTTP::Headers->new( %{$_} ) };

そうするかわりに、型変換用に「空の」サブタイプを作成できます。

    subtype 'My::HTTP::Headers' => as class_type('HTTP::Headers');

    coerce 'My::HTTP::Headers'
        => from 'HashRef'
        => via { HTTP::Headers->new( %{$_} ) };

型結合のかわりに型変換を使うこと

型結合のかわりに型変換を使うことを検討してください。これについてはMoose::Manual::Typesで詳しく取り上げています。

型の定義はすべてひとつのモジュールにまとめること

型や型変換はすべてひとつのモジュールの中で定義してください。これもMoose::Manual::Typesで説明しました。

ベストプラクティスの効用

このようなベストプラクティスにしたがうと、さまざまな利益が得られます。

ほかのコードとの相性が確実によくなるよう手助けをしてくれるので、コードの再利用性が高まり、拡張しやすくなります。

定評のある慣用句を使うことでメンテナンスが簡単になりますし(ほかの人にメンテナンスしてもらわなければならなくなったときは特にそうです)、コードをすぐに理解してもらいやすくなるので、ほかのMooseユーザからのサポートも得やすくなります。

また、中にはMooseが適切な処理をするのを支援するためのものもあります(特に不変化についての話がそうです。不変化すると、コードが速くなります)。

メタプログラミングを最大限に活用する手助けをするものもたくさんあります。本当の型変換を定義するかわりにnewをオーバーライドして手作業で型変換していては、イントロスペクション可能なメタデータは得られません。MooseXの拡張モジュールがイントロスペクションを頼りに適切な処理をしている場合は特にこのようなことが問題になります。

作者

Yuval (nothingmuch) Kogman

Dave Rolsky <autarch@urth.org>

コピーライト & ライセンス

Copyright 2009 by Infinity Interactive, Inc.

http://www.iinteractive.com

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.