Mooseのアトリビュートには、多くのオプションがあります。これらはハッシュとして渡すので、順不同で指定することが出来ます。
この指定順について、私はこれまで結構場当たり的に決めていました。軸足となるのはやはりMoose::ManualやMoose::Cookbookの例になりますが、一旦整理してオレオレルールというかオレオレガイドラインを考えてみようと思いました。
あまりにも毎回ばらばらだとコードが明快でなくなりますし、その逆で並び順次第でコードがより分かりやすくなるなら、やっておいて損はないものだと思います。
とはいってもこれはあくまでネタなので、どうか真に受けないようにしてください。
それでは、Moose 0.92時点でのオプション指定順私案をご紹介します。
metaclasstraitsisaccessorreaderwriterisadoescoerceweak_refauto_derefinit_argrequiredlazylazy_buildpredicateclearerdefaultbuildertriggerhandlesdocumentation
これらの並び順の意図などは、以下で補足しています。
原則
整理にあたっては、以下のような原則を敷いたつもりです。
- クラスを利用する側の視点に立つ
- 類似オプションはまとめる
- 汎化したオプションを先に、特化したオプションを後に
- よく使うオプションを先に
- 他のオプションを上書きするオプションは、上書きされるオプションの後に
一番大事なのは冒頭の原則です。自分がそのクラスのことを何も知らないと仮定して、コードを読み下すならばどんな順番で情報が提供されていると嬉しいか、それを第一に考えました。
自分が他人になりきるために、
- 大企業らしく「ためにするドキュメント」が山ほどあるのに、
- 真に必要な情報が全くなく、或いはS/N比が劣悪な膨大な情報の森に隠れていて、
- それらの情報や「情報の在処」が口伝やら一子相伝やらで属人的環境の内部で完結しているという素敵な職場で、
- 前任者が心を病んで退職したことによって口伝が失われたチームがあって、
- そこに自分が火消し役として飛び込んだ
......と脳内設定してロールプレイを試みてみましたが、何故かそういう想定がリアルに感じられる今日この頃です。
悲しい自虐ネタはさて措いて、その他は「まずは概論、続いて各論」「後出しじゃんけんをしない」という作法とも捉えることが出来ようかと思います。また、コードは往々にして成長するものであって、後になってオプションを追記する場合があると考えると、似たようなオプションが固まっていた方が何かと便利でしょう。
気を付けたいのは、後にあるから重要でない、という整理が成立しないことです。documentationなどの補足的なものを除けば、どれも重要です。ですが、オサレなディナーが食前酒やらオードブルから始まるように、順序立ててオプション(=料理)を提供する観点があってもいいのではないでしょうか。なお、私はオサレなディナーの体験がないので、上記は想像に基づく比喩であります。
もう少し大衆的に書くと
- 取り敢えずビール
- 挨拶
- 乾杯
- 歓談
- 中締め
- 歓談
- 一本締め
- 別の飲み屋で二次会
- カラオケボックスで三次会
- 締めは屋台でラーメン
という高カロリーな打ち上げでしょうか。これなら自信を持って書けます!(あれ、何の話をしていたんでしたっけ)
1. アトリビュート全体に関わる情報
metaclasstraits
アトリビュートそのものをいじるものなので、これら2つは最初に書きたいところです。
その二つの順番としては、metaclassは1つだけ、traitsは複数指定出来るので、metaclassを先にしておきたい。これはハンバーガー屋に行って注文する際に、サイドメニューよりもメインメニューを先に指定することに似ています。この方が「頭でっかち」にならず、据わりがよいように思えます。
2. アクセッサー関連
isaccessorreaderwriter
次に、そのアトリビュートへのアクセッサーを書きます。アトリビュート全体に関わる、メタ的な情報を除けば、アクセッサーの情報が最も基本となるからです。
isが最初なのは、isの設定が他のプロパティーで上書きされるからです。例えば、
has 'foo' => (
is => 'rw',
writer => '_set_foo',
);
のように。
残り3種のうち、accessorはreaderとwriterをまとめたものですので、汎用的なプロパティーと見なせるので先にします。
readerとwriterでは、読む頻度よりも書く頻度が低いので、readerを優先しました。
3. 型関連
isadoescoerceweak_refauto_deref
アクセッサーの情報を提供すると、読もうとか書こうとかという気になりますので、引き続いてアクセス対象のアトリビュートの型を規定します。つまり、読もうとする値がどんな型であるか、または、書こうとする値がどんな型でなければならないか、という情報を提供するのです。
型変換を規定するcoerceは、「isaで定義した型でなければ、変換を試みるよ」という情報なので、isaの後に来るのが自然でしょう。どんな型かという、読み書き両方に使う情報であるisaが先に来るべきです。
weak_refとauto_derefは、型とは直接関係ありませんがここで規定します。というのも、weak_refは「何らかのリファレンスを持つんだけれども、弱いリファレンスとして持つことにするよ」という情報なので、isaで「何らかのリファレンス」を規定した直後に来るのが分かりやすいです。auto_derefも同様で、「何らかのリファレンスを持つんだけれども、デリファレンスして返すことにするよ」という情報だと整理出来ます。
これらはcoerceと同様に、一種の内部的な振る舞いを規定する情報なのですが、coerceよりは頻度が低いことから、coerceの後に書くようにしました。
また、doesはisaと同じ位置にあるのが自然です(isaとは排他です)。
4. コンストラクター引数関連
init_argrequired
続いて、コンストラクター引数関連の情報です。
コンストラクター引数に何を与えようかという情報は、それはつまり型です。型とコンストラクター引数の順番が逆では違和感があります。
また、コンストラクター引数で値を設定する場合は、セッター(ミューテーター)で値を設定する場合の特殊例として捉えることも出来ます。特殊というのはやや過剰な表現ですが、「新たに作成して、それを使う」という場面を想定すると、そこに「(既にあるものを)使う」ことが包含されていることに気付きます。時系列から見れば、確かに「コンストラクターを呼んで、それから使う」という順番ですが、オブジェクトを自分で明示的に作るとは限りらず、出来合いのオブジェクトを使うことがあるので、やはり時系列にこだわらない方が良いでしょう。
この2つでは、「その引数が必須かどうか」を気にするより、まずは「どんなキーで指定すべきか」という情報の方が(APIの伝え方として)重要なので、init_argを先にしています。
5. 値の設定状況と遅延設定
lazylazy_buildpredicateclearer
値をただ使うだけでなく、値が存在しているか否かという情報が必要になることもあるでしょう。また、値を消去する場合もあるでしょう。情報を取得するpredicateが先、情報を更新する(この場合は値を消去する)clearerが後になります。clearerはAPIとして公開しない(頭に_を付ける)こともあるので、説得力が増される感じがします。
遅延設定というのは内部情報であって、APIの外側では知らなくて良い情報なのですが、lazy_buildが真だとpredicate, clearer, builderが自動的に設定される都合上、lazy_buildが先に来るのが道理です(lazyとlazy_buildを両方指定すること自体は、エラーにも警告にもなりません)。
また、lazyの場合でも、lazyしたからにはデフォルト値が必要になるので、後述のbuilderかdefaultよりも先行させる必要があります。lazyはpredicateやclearerは直接関係がないのですが、lazy_buildはlazyの強化版という位置付けなので、lazy_buildよりも先に来るのが妥当です。
6. デフォルト値関連
defaultbuilder
上記でlazyやlazy_buildが来たので、デフォルト値はここで規定します。両方一緒に指定することは出来ないので、この2つでは順番という概念自体がありません。
両方指定すると、Class::MOP 0.94 (+ Moose 0.92)では、以下のようなエラーとなります。
Setting both default and builder is not allowed.
また、initializerについては、それを使わずにbuilderを使うべきなので、順番にリストアップすること自体をしていません。
7. トリガー
trigger
トリガーはここで登場させます。
APIの内部で完結する処理ではあるのですが、前の記事でご紹介したように、自分のアトリビュートpasswordと別のアトリビュートhashed_passwordが足並みを揃えた値であることを宣言する意図を持つことが往々にしてあります。
これは一種の「アトリビュートはどんなものか(属性)」という情報なので、これまでのis以来続いてきた流れに乗ってここで書くのが自然です。「アトリビュートで何が出来るか(振る舞い)」というhandlesの後に書いたのでは、振る舞いの後に属性が再登場するようで、ちぐはぐな感じを受けてしまいます。
8. 委譲
handles
これまで連綿と書いてきた「属性」の情報ではなく、「振る舞い」の情報になる委譲の規定は、ここでようやく出現します。
アトリビュートの「状態」という固まりを定義した後に、「ところでこれまで定義したこのアトリビュートなんだけど、こういうAPIを提供するよ」という情報が出て来るのが自然でしょう。メソッド(sub)よりも先にアトリビュートを書く(has)ことと同じです。
9. 文書化
最後に、コードにドキュメントを埋め込むという観点で、documentationを規定します。「そのアトリビュートが何であるか」という情報なので、冒頭に書いた方が良いのではないかとも思いましたが、敢えて最後に書きます。
というのも、初っ端からダラダラと文字列を重ねてしまうと、Mooseで宣言的に書けるコードの小気味よさを削いでしまうのではないか、と思ったからです。
その値が何であるかという端的な情報は、そもそもアトリビュート名で持つようにするのが、自己説明的な良いコードです。そしてそれはオプションに先行して記述されます。
has 'name' => (
is => 'rw',
isa => 'Str',
documentation => q{Customer's name (without title)},
);
何とも投げやりなdocumentationですが、ともあれ、そのアトリビュートが「名前」であるというのは、いちいちdocumentationを見るまでもなく、hasの右隣に書いてあるのでそれで事足ります。
また、isやisaなどで表現しきれない情報を書くという観点や、敢えて指定したオプションについての言及をするという観点に則ると、やはり端的なオプション指定の後に来るのが得策だと思います。
拡張オプション
オプションを拡張するようなトレートを適用した場合は考えどころで、これもオレオレ基準を作っておきたいです。
あまり好例が思いつかないのですが、例えばMooseX::Aliasesによるaliasオプションならば、アクセッサー定義系だと整理することが出来るので、is, accessor, reader, writerの後に添えるのがよいのではないでしょうか。
また、Moose::Cookbook::Meta::Recipe2などにあるlabelオプションならば、例えばウェブアプリケーションを想定した場合で、アトリビュートnameの入力欄であるinput要素の傍にいるlabel要素の値として使うことを想定した場合に、documentationの前辺りに細々と書いておくのが良いと思います。そうでないと、ロジックを規定する記述に表示関連の記述を混ぜ込んでしまうからです。
なお、余談ついでに言及しておくと、ロジックと表示を分離する観点に立つと、labelに生の文字列を入れるのは気持ち悪いです。実際にはラベル識別子のようなものを設定して、(MVCの)ビュー側で$item->label_of_nameなどとしてリソース情報(*.po由来の*.moなり、DBなり)から生の文字列を引っこ抜いてくるようにすると、後々楽になります。その際、ユーザーの設定言語を使うようにすれば、国際化にも容易に対応出来ます。
まとめ
CSSであれば、益子貴寛さんの『Web標準の教科書 - XHTMLとCSSでつくる"正しい"Webサイト』(pp.403-404)などでも紹介されているように、Mozilla.orgによる案(http://www.mozilla.org/css/base/content.css)があります。PerlのTMTOWTDI(「やり方は一つではない」)な文化が好きなのですが、かといって完全な自由だと不安になる私は、拠り所となる物が欲しかったので、取り敢えず叩き台を作ってみることにしました。
指定順に正解などはなく、人それぞれの方法があります。オレオレガイドラインが正しいだなんて、欠片も思っていません。「べき」「それが自然」「妥当だ」などと断定調で書いていますが、これはあくまで自分向けの台詞のつもりです。Mooseで宣言的なプログラミングをしていると、断定調で「AはBだ。CはDだ。EはFとGが出来る」などと書けてしまうので、その癖がつい出てしまいました(本当ですか)。
また、この柔いオレオレガイドライン自体も、他の多くのコードを拝見させていただく体験を通じて、今後に考えが変わって修正しないとも限りません。いわば緩やかな指針として、半ばネタ的に整理してみたというのが事の次第でした。
もし何か、
- 「自分はこうしているよ」
- 「そこの順番は逆だろう、常識的に考えて」
- 「おいおい、hogehogeオプションが漏れているぞ」
- 「私も金融ユー子ですが、迫真のロールプレイが他人事に思えなくて貰い泣きしました」
......などのご意見・ご感想・ご提案・その他諸々がありましたら、このブログやSBSのコメントなどで教えていただけると幸いです。
余談ついでに......アトリビュートの豊富なオプションはあなたの武器です
改めてひっくり返してみると、Mooseのアトリビュートが持つオプションの豊富さに驚かされます。こんなに沢山オプションがあると、何かわくわくしてきますね。初めてMooseのマニュアルを読んだとき(当時はまだまとまった日本語訳はありませんでした)、不十分な理解ながらも、これまでやってきた或る種の「ためにする処理」の有象無象へばっさばっさとDSLで切り込んでいく感じを受けて、かなり興奮したことを思い出しました。それは、初めて標準Cライブラリーについての文書を読んだとき、ソフトウェアを使う側から作る側への橋を渡り始めたときの興奮にも似た感触でした。
こんな記事をここまで読んだ方でMoose/Mouseを使っていない方は殆どいらっしゃらないと思いますが、もしまだMoose/Mouseを使ったことがない方でも、オプションの多さに圧倒されて萎えないでください。これらはあなたを悩ませるために敵陣から飛んできた矢ではなくて、あなたが武器として使えるように矢筒に入った矢なのです(そうら、ファットカンマが矢に見えてくる見えてくる......)。
アトリビュート以外にも、Moose/Mouseという武器庫には多くの武器がまだまだあります。Moose/Mouseを使って、めくるめくポストモダンオブジェクト指向プログラミングの世界を体験しましょう!
コメントする