- 題名
- 推薦の言葉
no Moose
と不変化new
はオーバーライドしないこと- かならず
SUPER::BUILDARGS
を呼ぶこと - できればかならずデフォルトを用意すること、そうでなければ
required
を使うこと - よほどのことがなければ
default
のかわりにbuilder
を使うこと lazy_build
を使うこと- クリア用のメソッドや断定用のメソッドはプライベートにすることを検討すること
- デフォルトは読み取り専用にして、書き込み用のアクセサはプライベートにすることを検討すること
- サブクラスでアトリビュートの型を変える前にもう一度考え直すこと
initializer
機能は使わないことauto_deref
のかわりにMoose::Meta::Attribute::Nativeトレートを使うこと- もっとも具体的なサブクラスであってもかならず
inner
を呼ぶこと - 型には名前空間を付けること
- Mooseの組み込み型は直接型変換しないこと
- クラス名を直接型変換しないこと
- 型結合のかわりに型変換を使うこと
- 型の定義はすべてひとつのモジュールにまとめること
- ベストプラクティスの効用
- 作者
- コピーライト & ライセンス
題名¶
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::cleanやnamespace::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
を呼ぶこと¶
augment
とinner
を使う場合は、階層内でもっとも具体的なサブクラスの中でも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 <[email protected]>
コピーライト & ライセンス¶
Copyright 2009 by Infinity Interactive, Inc.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.