Play!の黒魔術を読み解こうとしてみる(未完

この記事は、Play! framework Advent Calendar 2011 jp #play_ja : ATNDの11日目の記事です。

さて、軽めに行きましょう!(ということにさせてください…

僕とPlay!

とあるWEBサービスの受託開発案件があり、自分を含めてメンバー的にJavaプログラマが多かったので、Javaが必然。で、環境面も含めてモロモロがフルスタックでサポートされてるフレームワークはないかなーということで、Play! Frameworkを使うことになりました。

2ヶ月ばかり携わったあとで私はその仕事を離れることになったのですが、無事サービスは開始されたようです。めでたしめでたし。

初めのPlayの印象は「黒魔術」の印象でしたが、それはControllerとviewの結合部分が黒いくらいだけで、それ以外の部分は案外素直な作りではあるので、学習にかかるコストは恐ろしく低く済みました。

で、せっかくなので、その黒魔術の部分について少し読んでみようとして、結局読みきれなかったんですが、書いていきます。

バージョンは1.2.3。ソースコードはすべて https://github.com/playframework/play から引用しています。

Controller.render(Object…args)

viewを描画するときに呼ぶのがControllerのrenderメソッドなわけで、引数として渡す変数名をview側の識別子として使えるわけで、なんとまぁ書きやすいけど不思議な部分なわけです。さて、行きましょう。

    protected static void render(Object... args) {
        String templateName = null;
        if (args.length > 0 && args[0] instanceof String && LVEnhancerRuntime.getParamNames().mergeParamsAndVarargs()[0] == null) {
            templateName = args[0].toString();
        } else {
            templateName = template();
        }
        renderTemplate(templateName, args);
    }

引数の確認をして、配列の先頭がテンプレート名かどうか判定しつつ、renderTemplate(templateName, args); に渡しています。次。

    protected static void renderTemplate(String templateName, Object... args) {
        // Template datas
        Map<String, Object> templateBinding = new HashMap<String, Object>(16);
        String[] names = LVEnhancerRuntime.getParamNames().varargs;
        if(args != null && args.length > 0 && names == null)
            throw new UnexpectedException("no varargs names while args.length > 0 !");
        for(int i = 0; i < args.length; i++) {
            templateBinding.put(names[i], args[i]);
        }
        renderTemplate(templateName, templateBinding);
    }

引数argsからMap templateBindingを作って、renderTemplate(temlateName, templateBinding); に渡しています。雰囲気的に、String[] names = LVEnhancerRuntime.getParamNames().varargs; が怪しいようです。

        public static ParamsNames getParamNames() {
            Stack<MethodExecution> stack = getCurrentMethodParams();
            if(stack.size() > 0) {
                MethodExecution me = getCurrentMethodExecution();
                return new ParamsNames(me.subject, me.paramsNames, me.varargsNames);
            }
            throw new UnexpectedException("empty methodParams!");
        }

スタックの内容があれば、MethodExecution meの内容から、ParamsNamesインスタンスを作って返しているようです。ParamsNamesはデータを扱う程度のクラスで、そのなかのvarargsが、viewに渡る識別子になる雰囲気です。

ということで、meに代入しているgetCurrentMethodExecution()。

        protected static LVEnhancer.MethodExecution getCurrentMethodExecution() {
            Stack<MethodExecution> stack = getCurrentMethodParams();
            if(stack.size() > 0)
                return stack.get(stack.size() - 1).currentNestedMethodCall;
            throw new UnexpectedException("empty methodParams!");
        }

さっきのgetParamNames()と同じように、getCurrentMethodParamsスタックを扱っているようです。で、そのスタックの最後のもの(Stackクラスならpeekと同義???)の、currentNestedMethodCallメンバを返しています。うむー。

で、そのメンバをセットしているのは1箇所で、LVEnhancer.LVEnhancerRuntime.initMethodCall。

        public static void initMethodCall(String method, int nbParams, String subject, String[] paramNames) {
            getCurrentMethodParams().peek().currentNestedMethodCall = new MethodExecution(subject, paramNames, nbParams);
            Logger.trace("initMethodCall for '" + method + "' with " + Arrays.toString(paramNames));
        }

先ほどから見るgetCurrentMethodParamsスタックをpeekして、currentNestedMethodCallを設定しているようですが、これ以上はEclipse上では動作を追うことができません。

ということで「initMethodCall」でgrepしてみると、LVEnhancerクラスのenhanceThisClassメソッドに、以下の記述が見つかりました。

                        stmt.append("play.classloading.enhancers.LVEnhancer.LVEnhancerRuntime.initMethodCall(\"" + dmio.getName() + "\", " + dmio.getNbParameters() + ", " + (methodParams.subject != null ? ("\"" + methodParams.subject + "\"") : "null") + ", $$paramNames);");
                        stmt.append("}");

                        insert(stmt.toString(), ctClass, behavior, codeAttribute, iterator, frame, false);

メソッド呼び出しのようなものが、文字列で書かれています。メソッドの先頭ではjavassist.CtClassを使っているので、Javassist*1を使ってバイトコードの変換をしているようです。

雰囲気的に(こればっかだな)、initMethodCallメソッドの最終引数paramNamesに渡すために埋め込んでいる、"$$paramNames"が怪しそうです。

                        DecodedMethodInvocationOp dmio = (DecodedMethodInvocationOp) frame.decodedOp;
                        StringBuffer stmt = new StringBuffer("{");
                        MethodParams methodParams = DecodedMethodInvocationOp.resolveParameters(frame);
                        stmt.append("String[] $$paramNames = new String[").append(methodParams.params.length + (methodParams.varargs != null ? methodParams.varargs.length : 0)).append("];");

DecodedMethodInvocationOp.resolveParameters(frame)から取ったものをゴリゴリして、String配列を定義するコードにしています。

まとめのようなもの

…とまぁ、追えたのはここまででした。この先は、JavassistのFrameからTrackableArrayを取り出してLocalVariableクラスをごにょごにょしてるらしいです。雰囲気的に。

LVEnhancer#enhanceThisClassはEnhancerクラスの抽象メソッドの実装で、抽象メソッド定義部分のコメントを見てみると、

    /**
     * The magic happen here...
     */
    public abstract void enhanceThisClass(ApplicationClass applicationClass) throws Exception;

とあるので、やはりここが臭いますよね〜という程度で、やんわりと〆ることにします。

次は [twitter:@tan_go238] です。よろしく!

豚スペアリブの素朴なシチューは簡単で美味しいから覚えておくべき

じゃがいも、にんじん、玉ねぎ、パセリ、豚スペアリブ。調味は粒胡椒と塩。これだけ。

あとは、保温性の高い鍋。今回はホーロー鍋を用意したが、小さかったので、後で鉄鍋に入れ替えている。最も向いているのは土鍋だけど、うちにあるのはなぜかバカでかいので、今回は登場しない。

じゃがいもは皮をむく。で、鍋に入れておく。

にんじんは皮をむいて、適当に切っておく。玉ねぎもまた皮をむいて、これも適当に切っておく。で、これも鍋に入れておく。

パセリは葉の部分をちぎっては投げ、ちぎっては投げ、鍋に入れる。茎の部分も入れるが、茎は食べないことにする。

豚スペアリブは塩をまぶして、これも鍋に入れる。塩は多めでよい。

粒胡椒をそのまま入れて、塩を足す。鍋に入れる順番はこの限りではないので、準備ができたものから投入すればよい。

かぶるくらいに水を入れて、火にかける。沸騰したらそのままグツグツしておくとアクが集まるので、2回くらいとっておく。

ここで味見をして、塩を調整しておく。自信がなければ少なめにしておいたほうが無難っちゃ無難だが、できればここで塩を決めておいたほうが仕上がりの一体感がでる。ここは経験ですね。

で、蓋をして、火を止め、遊びに行く。

夕方帰ったら、火を再び点火し、沸騰したら出来上がり。もし、鍋の蓋を開けて脂の層が浮いていれば、固まっているうちに取っておく。脂はパセリの風味があって美味しいので、冷蔵し、炒め油などに利用すればよい。



で、鍋をそのまま食卓に供する。好みで胡椒やオリーブオイル、バター、サワークリームマスタードなどを添えてもよい。驚くほど美味しい。粒胡椒まで柔らかい。

出来上がりまでの時間は長いが、調理にかかるのは長くて30分程度。写真の材料は4皿分程度だが、10皿分でも手間はそれほどかからない(むくじゃがいもの量が増える程度、ピーラーが大活躍)なので、例えば、友人兄弟家族が子供を連れて遊びに来て〜なんて場面ではちょうど良い。小さい子どもにも人気がある。朝のうち、もしくは前夜に仕込んでおくだけで、帰ってからの仕事が激減する。

肉は豚スペアリブを使ったが、牛肉でも美味しい。スネ、モモ、肩ロース…。鶏肉ならモモぶつ切りとか、手羽先とかを混ぜてもよさそう。

豚バラは避けたほうがよさそう。柔らかくなるまでに時間がかかる。これは経験でしかないんだけど、放置する調理法は赤身を柔らかくするが、脂の塊はあまり溶かさない。多分、脂部分は90度以上とかの高温で煮込まないとダメなんじゃあるまいか?ただしその温度だと、今度は赤身の水分が抜けやすくなるので、温度管理と加熱時間の調整が必要になる。肉部分がパッサパサなダメ豚角煮を食べて、ガッカリしたことがあるだろ?

まぁ、つまり、豚スペアリブは無難なわけですわ。

他、野菜としてセロリを入れても美味しい。プチトマトを入れると具になるし、トマト缶を入れるとトマトシチューになる。他に合いそうなのは、キャベツとか、大根やカブ、下仁田ネギなんかも良さげですね。ローリエなどハーブを足してもよい……が、ついつい「いつも使っています!」という人は、一度入れずに作ってみることをおすすめする。強くおすすめする。料理は、何を足すかよりも何を引くかなのだ。

あと、重要なのは鍋。保温性の高いものを使う。土鍋が安くて手に入りやすいので、なければ一つ買っておくとよいですよ。

おまけ

愛用している、いわゆるI型ピーラー。にんじん等の長物は向こう側に押し出して、じゃがいもや梨などの丸物はナイフのように使う。oxoのは安価で、刃渡りが長くて使いやすいですよ。

OXO 皮むき器 たて型ピーラー

OXO 皮むき器 たて型ピーラー

大学イモは、水あめで作ると失敗しない

さつまいもって、意外とレシピに困るのは俺だけでしょうか?

サツマイモの皮を適当にむき、乱切りにして5分くらい水にさらす。水気を切って、中温の油で揚げる。

水飴適量を鍋で温めて柔らかくして、揚げたサツマイモと黒ごまを入れ、からめる。油を塗ったバット…なんて面倒なので、テフロン加工のフライパンに広げて、熱がとれたら皿に盛る。







カリッと仕上げたい場合は、二度揚げするといいかもしれない。揚げ物は程よい水分があるほうが良い感じに揚がるので、揚げたサツマイモを冷凍する、一度揚げ後に水に浸す、霧吹き、揚げる前に茹でてしまうなど、色々やってみるとよい。自分はやらない。面倒だし。

水飴はあめの俵屋 - のれんを守って百八十余年。お土産に、ご贈答に。のじろ飴を使用。カタログでは500gで2,700円と高価だが、容器持ち込みの量り売りで700gを1,700円で買うことができた。らしい。砂糖を溶かしても良いが、加減が難しいんだよ。

ということで、転職しました。

今日から、株式会社ECナビで働くことになりました。

思ってた以上にすごい人達に囲まれてたりして、「もっとも下手なプレイヤーであれ」という言葉を思い出します。

ありがとう北陸、こんにちは東京。今後ともよろしくお願いいたします。

ネット難民のため、取り急ぎ…。

長めの夏休みに入ります

さて、振り返るか。

良かったこと

  • 客先対応からプログラミングまで、チームリードからオフショア開発依頼まで、ひと通りの経験ができた
  • 部署として組織の規模はそれほど大きくなく、経営層まで気軽に話せた
  • 仕事によっては、フレームワーク選定レベルから判断できた
  • 何が必要か等、相談できる上司はいた
  • 就業時間の管理がキッチリされていて、残業は多くなく、サービス残業はなかった
  • 有給休暇が取りやすかった、というか普通に取ってた
  • サーバなど(大枠で一旦稟議を通してしまえば)あとは自由に使っていた

良くなかったこと

  • 開発標準などの社内ルールの是非についての議論する場がなかった
  • WEBアクセスフィルタが導入され、ネットワークが遅く、情報収集が制限されていた
  • (特に中途採用された)上司とのギャップが大きかった
  • 受託開発業は、開発予算は案件単位で、ライブラリやパッケージなどを開発しにくかった
  • 上記状況なのでどうしても以前の案件の知識に頼ってしまうため、5年前の基盤を使いまわすような悪循環があった
  • ダメ出しをしてくれる人が少なかった
  • ドキュメント重視の守りの開発スタイルを良しとしているが、土壇場で守られた記憶がなかった
  • 「平均的」という自分に対する評価の根拠が示されなかった
  • 何が得意かとか自分の売りとかよくわかっていない
  • 一次請けとの間に挟まれて問題を起こしたことも数回
  • 根拠の薄い見積りと計画を出して、2ヶ月くらい大変だったことがあった
  • 悪いニュースをうまく伝えようとして、できず、報告を遅くしただけだったこともあった
  • 良くないことは、環境が云々など外部要因に頼りがち
    • というのは、上記は思いついた順に書いているんだけど、如実に出ているんだよ…これは恥ずかしいなぁ
  • 出張続きだったころ、娘に寂しい思いをさせてしまったよう
  • 残業や出張続きだと、妻とも喧嘩が増えるパターン

これから目指したいこと

  • 攻めのお仕事を。と言いつつも、守りも大事なので、その辺の見極めを付けられるようになりたい
  • 仕事が行き詰まったときの対処を、もう少しうまくできるようになりたい。
    • 客先とモメそうになったときだけじゃなく(そういう状況がこれからあるかわからないけど)、「この仕様どうしたらいいかわからん」「このライブラリの使い方よくわからん」とかそういうレベルも
  • 自分は何が得意か、何がやりたいかなど、もう少し見極められるようになりたい
    • そのために、オープンな活動は維持していきたい
  • 妻子とは、仲良くやっていきたい

もっと具体的に目指すところとか色々書いてもカッコいいだろうなーと思いつつ、まぁ自分はこんなものです。

私自身が4回転職し、この部分は非常に耳が痛い話です。自分が何をやりたいかとうことは、若い頃には明確であった訳ではなく、色々な職場を経験して、徐々に明確になってきている気がしています。
書籍『Being Geek』(3):柴田 芳樹 (Yoshiki Shibata):So-netブログ

で。

自分自身、追いつめられて焦って空回りしてしまうこともあったりするので、追い詰められつつユルく構えられることができたらなーと思ってますが、まぁなんというか、実際に自分が力を出せるのはそういう状況ではないという気もする*1ので、これからも悩み多き人生は続くだろうと思いながら…。悟りを開く日は果たして来るのでしょうか?

テスト駆動開発がーとか、アジャイル開発がーとか、SI業界の未来がーとかいろいろあるかもしれないけど、結局、自分はそこで何をするか、だけですね。そのための何かを語る明確な言葉は持ってないです。少なくとも、現在は。

あー、何に面白さを見出したかくらいは、明確にしておいたほうがいいなぁ。

*1:「aozora21 自分が自然に出せる音程とイメージしていたのと違っていたんじゃないのかなあ??」http://b.hatena.ne.jp/entry/anond.hatelabo.jp/20110719213544 的状況