Born Too Late

Yuya's old tech blog.

Spika Hackathon に参加してきた

2013-10-21 00:13:00

久々のブログです。

夕方に3DS LL とポケモンのセットが届いたのですが、電源がついてなくてあんまり遊べてません。

今週末は @kuzuha さん主催での Spika Hackathon というイベントに参加してきました。

Spika がどういうものなのかはこの辺の記事を見ればいいと思います。

TechWave に記事が紹介されて直後から、コードの品質がヤバいと話題になっていました。

僕自身も以下のような印象を持ちました。
(アッ、メッセンジャーの綴りが違う...)

試みは面白いと思うけどコード品質酷過ぎる気がする。 PHP のバックエンドしか見てないけどテストとか全くないし。ある意味「アプリ界のWordpress」感はすごいある。 http://t.co/V20USW78bz #messanger #spika #ios

— Yuya Takeyama (@yuya_takeyama) October 8, 2013

という具合に割と強めに Dis ったりはしたものの、ちょうど同じ頃に以下のようなツイートを見ていたこともあって、せっかくだからいろいろなおしてみるかという気になったのでした。
(これはまた別の会社のゲームフレームワークについての文脈だったと思うけど)

まぁ、本当に実力ある人は小馬鹿にしたり皮肉言う前に「こんくらいやっとけバカ!」ってコミットメッセージとともにプルリク投げるんじゃないかと思いますよー

— ぼうくん (@VoQn) September 26, 2013

この記事では送ったプルリクの詳細について残しておこうと思います。

#6 added CI base

これは参加者 3 名による複数の修正が含まれていて、以下のような内容を含みます。

Travis CI はそろそろ説明不要の感もありますが、日本 (というか PHP コミュニティ) ではあんまり流行ってないのかな、と思われる Coveralls について簡単に説明すると、コードカバレッジレポートを残すための PaaS です。
Coveralls に GitHub の OAuth を使ってサインインし、計測対象とするリポジトリを登録しておき、適宜設定を行うと Travis CI のビルド後にレポートが送られるようになります。
Ruby だと Gemfile に coveralls の gem を追加するぐらいで使えて便利です。
PHP だと @satooshi_jp さんによる php-coveralls というライブラリにより利用できますし、その他の言語でも大体使えるようになってきていることと思います。

このプルリクにより、ユニットテスト・継続的インテグレーションの基盤がとりあえずできました。

[caption id="attachment_2201" align="alignnone" width="557" caption="プルリクを送ると自動的にカバレッジレポートが送られるようになる"][/caption]

[caption id="attachment_2204" align="alignnone" width="533" caption="テストが実行されている場所・されていない場所が一目でわかる"][/caption]

WebTestCase を使ったユニットテストについては、前提として Spika の中の人が develop ブランチで Silex への移行を予めやっていた、というのが大きかったです。

#7 Change directory structure

PSR-0 の規約に従ってディレクトリ構造の変更を行い、 composer.json にもオートローディングの設定を行いました。

[caption id="attachment_2211" align="alignnone" width="604" caption="規約に沿った命名にすることで require_once が不要になる"][/caption]

これは地味ですがやっぱり重要だと思います。
require_once を書かなくてよくなる、というだけでも QOL が向上しますね。

#11 データベースのインターフェイスを追加

Spika はバックエンドのストレージとして CouchDB を使っていて、それを隠蔽した SpikaDBHandler クラス (現 CouchDb クラス) というのがあるんですが、その public メソッドだけを抜き出して DbInterface というインターフェイスを定義しました。

インターフェイスを定義することで、それに沿った実装さえ用意すればバックエンドのストレージを容易に置き換えられる、というのもありますが、とりあえずはユニットテストを書く上で便利、というのもあります。
データベースに依存したクラスであっても、インターフェイスを元にしたモックオブジェクトさえあればオンメモリで動作するユニットテストが書けるようになります。

#15 Dependency Lookup ではなく Dependency Injection を使用する

Silex による開発で割とやってしまいがちな間違いは Dependency Lookup により依存オブジェクトを取得するようなモジュールを書いてしまうことだと思います。

どういうことかというと、大体以下の記事に書かれているのでした。

Pimpleでシンプルに正しくDIを理解する

この記事では Service Locator という言葉が使われていますが、Service Locator による依存性の解決は Dependency Lookup です。

#19 $app['beforeTokenChecker'] のモジュール化 & ユニットテスト

いくつかの API では、事前処理としてアクセストークンによる認証を行っているのですが、その認証機構が無名関数により実装されていて、ユニットテストが全くかかれていない状態だったので、クラスとして再実装しました。

前提として、#11 においてデータアクセス部分をインターフェイス化していたからこそテストが書きやすかった、というのがあります。

ところで、Silex の Before/After Middleware という機構はちょっと使い辛いんじゃないかなーと...
その辺を解決する仕組みとして、WSGI/Rack/PSGI あたりを意識した Stack というフレームワークがあって、これを使えば大分いい感じになりそうなんですが、要求される PHP のバージョンが 5.3 から 5.4 に上がってしまうので、とりあえずやめました。

まとめ

レガシーな PHP アプリケーションをリファクタリングする上で、それなりに汎用性の高い知見が得られた気がするので、まとめておきます。

人生楽して生きたいものですね。

関連記事

MongoDB の MapReduce を Ruby の DSL で定義するフレームワーク Mr. Mongo 作った

2013-04-08 03:00:35

500 円玉貯金が N 万円貯まったので Thunderbolt Display を購入しました。
現在の開発環境の様子です。
画面が広いと Vim で複数ファイル広げたり、ファイル更新時にはその横でユニットテストが自動実行されたり、別のペインで tig や htop を実行しておくこともできるので便利ですね。
あとは最近少女革命ウテナ DVD-BOX を買ったんですが、これも大画面で観られるようになりました。

そんな大画面でつくった gem を紹介します。

Mr. Mongo とは

yuya-takeyama/mr_mongo

MongoDB の MapReduce の定義を Ruby の内部 DSL で記述し、実行するためのフレームワークです。
Ruby で、といっても map/reduce の関数は JavaScript です。

例えばワードカウントであれば、以下のような記述になります。

map/reduce を __END__ 以降に記述できるのが特徴です。
ここでは使われていませんが、finalize も同様に定義できます。

なお、__END__ 以降の読み込みについては先日開発した InlineTemplateLoader を使用しています。

何で DSL が必要か

ひとつは書き方・定義の仕方を統一するため、もうひとつはユニットテストのためです。

書き方を統一する

MongoDB 標準のやり方にしろ、各言語向けのドライバを使うにしろ、そのままだと人によって書き方がいろいろ異なります。
そこにこの Mr. Mongo を導入することで、「1 ファイル 1 ジョブ」というルールが強制されるようになります。

MapReduce をユニットテストするということ

MongoDB における MapReduce のユニットテストについては、ほとんど言及されることが無いと思いますが、やはり重要なんじゃないかと考えています。
上記のような単純なワードカウント程度の例ならともかく、大量のデータを、複雑に処理し、しかも長期的にメンテナンスを行うのであれば、回帰テストとしてのユニットテストが無くては安心できません。

また、テスト駆動で開発することでより効率よく開発できるケースもあるでしょう。

Mr. Mongo でどうユニットテストを書くか

これについては現状でもできなくはないのですが、フレームワークとしてのサポートがまだ貧弱な状態です。
mr_mongo-rspec-matcher なんてのを作って RSpec でのユニットテストをやりやすくする、というアイディアはあるものの、まだ実装できていません。

一応現在のコードベースでも MapReduce のユニットテストはできています
MapReduce をオンメモリで実行し、その結果を連想配列と比較しています。

これをもっとフレームワークっぽいやり方でできるようになれば、いろいろ捗るんじゃないかと思っています。

あと、以前作った mongo_require.js を併用することで、MapReduce ジョブをある程度モジュール化して記述すれば、これもまたメンテナビリティに寄与すると思われます。

まとめ

MongoDB の MapReduce を効率よく開発・管理するためのフレームワーク Mr. Mongo について紹介しました。

おやすみなさい。

CakePHP の alphaNumeric バリデータにパッチを送った話

2013-03-17 14:20:46

ちょっと CakePHP を使う機会があったのでソースコードを眺めていたところ、ちょいとしたバグが見つかりまして、すぐにパッチを書き Pull Request を行ったところ、ものの数時間でマージされました。

バグというのが、「alphaNumeric バリデータにおいて、改行で区切ってしまえばどんな文字列も通してしまっていた」ということです。

修正は以下の 1 コミットのみ。

Fix alphaNumeric validation · 14c81fe · cakephp/cakephp

preg_match や preg_replace をはじめ、PCRE を使った正規表現で、このような 1 行の文字列にバリデーションを行う際は、正規表現の修飾子として D (PCRE_DOLLAR_ENDONLY) を使用しないと、末尾の改行に対して検出漏れが発生してしまいます。

それどころか、m (PCRE_MULTILINE) なんてつけていると各行に対してパターンマッチングを行い、preg_match の場合はどれかひとつでもマッチすれば 1 を返すので、例えば以下のような文字列も OK、という恐ろしいことが起こってしまいます。

$result = preg_match('/^[\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]+$/mu', "abc\n<script>alert(1)</script>");
var_dump($result);
// => int(1)

あくまでユニットテストレベルでの検証ですが、このような恐ろしいことが起こっておりましたので、早いところ次のリリースが行われることを願うばかりです。
CakePHP 2.3.2 当たりで取り込まれることが予想されます。

こういう正規表現を書いていると徳丸先生方面から斧が飛んで来る可能性があります。
予め徳丸本等を読んで対策を行うことをオススメ致します。
あとは PHP のマニュアルとか PCRE 自体のマニュアルの PASSING MODIFIERS TO THE REGULAR EXPRESSION ENGINE 付近とか。
(なお、徳丸本の 81 ページによると ^ の代わりに ¥A を、$ の代わりに ¥z を使って、「行の」先頭・末尾ではなく「データの」先頭・末尾を示すようにせよ、とあります。)

なお、その他のバリデータも同様の問題をはらんでいる可能性もありそうですが、普段 CakePHP を使わない私としてはそこまで見切れないので、仕事等で CakePHP を利用している方は、是非とも contribute してみてはいかがでしょうか。