2013年9月30日月曜日

akkaのActorを抽象化する方法を試行錯誤する

akkaを使ってActorをゴリゴリ作ってると、当然Actorを抽象化したいという要求が出てくるよね。

例えばこんな方針で抽象化を試みる。

  • Actorを継承したtraitを作って、共通処理を抽象化する。
  • 作ったtraitは型パラメータを持たせて、具象クラス化するときに例えば受け取るメッセージの型を指定する。



なんでわざわざ型パラメータなんて持たせてるかというと、これをやりたい。



要するに、送受信するメッセージは処理を内包しないただの型(case class MyMessage)にしといて、その型の挙動は型クラスのインスタンス(MesShow)で制御したいというわけ。

ところが、この実装には問題がある(実際に上記コードはコンパイルが通らない)。

  • akkaのActorにおいてメッセージはAny型で渡されるのでRuntimeにしか型判定できない
  • traitの型パラメータはコンパイル時に型消去されるのでRuntimeでの判定には使えない

=> つまり、上記コードの「case m: MesType」という判定はできない、となる。

これを解決するためには、

  • Anyで渡ってくるメッセージはobj.getClassを経由してRuntimeにクラス情報を取得する
  • 型パラメータの方は、TypeTagを渡すことでRuntimeまでクラス情報を引き継ぐ

とやるしかない、と思う。たぶん。

で、書いてみたのが以下のコード。



TypeTagをわざわざ指定してやらないといけないところとか、asInstanceOfでダウンキャストしてるところとか、カッコ悪い。

というわけで、「こうやるともっと綺麗に書けるよ」とか、「そもそも方針としてこうやるべき」とかあったら教えてください!

2013年9月16日月曜日

台風で身動きできなくなったからTDD勉強会開いた

昨日(9/15)から大学時代の友人である@tau06のウチに@minismahnと泊まりで遊びに行ったら、予想通り今日(9/16)の午前中は見事に台風直撃で電車動いてなくて身動き取れなくなったので、TDD勉強会などを開いてみた。

7月のTDDBC東京のときのお題、飲み物自動販売機 Ver 2.0をお借りして、実践しつつTDDってこうやってやるんだよー的なことを説明してから、2人でコード書いてもらった。
2人とも、別に俺が隅から隅まで教えなくても勝手にどうやったらいいか考えてやってくれるってのはわかってた。で、予想通り後半暇になってきたので、俺ももう1回改めて同じ課題をやってみた。

やっぱりStateモナドが正しかった


7月のとき、最初にお題見た時から「これStateじゃね?」とは思っていたんだけど、僕は初対面の人とペアプロやっていきなり「Stateモナドで行きましょう」とか言い出すこわい人じゃないので、さすがにやらなかったんですね(@razonさんにそれを言っても問題なかったかもしれないけど)。

というわけで、今日書いたコード全貌はこちら。
https://gist.github.com/nisshiee/6578338

ある瞬間のスナップショットとしてのVendingMachine型を用意するところまでは7月と一緒。で、前回はここから「VendingMachineを引数に受け取って、次の状態を表すVendingMachineを返す関数」を実装していったわけだが、今回は「State[VendingMachine, _]」を実装していく。あ、言い忘れたけどStateはScalaz使う。

Stateを作るのは簡単で、Stateオブジェクトのapplyメソッドを使えば良い。
引数fには、「処理前の状態Sを受け取り、処理後の状態S'と処理に伴って出力される値Aのタプルを返す」関数を渡してやれば良い。



例えば、現在の投入金額合計を取得する処理は、状態を表すVendingMachineには変化を与えず、金額だけをIntとして取得するStateとなるので、以下の様になる。



コインを投入する処理の場合は、VendingMachineの投入金額合計を加算して、処理に伴う出力は特に無いStateとなる。ただし、投入するコインの種類によって加算する額を変えたいので、一段高階にする。つまりStateそのものをvalで定義するのではなく、「Money => State」を定義する。



で、用意したStateを実行(?)するときは、まずいつもどおりモナドをfor式なりmapなりflatMapなりで結合して、最後にapply(処理後のSとAを両方取得)なり、eval(処理後のAを取得)なり、exec(処理後のSを取得)なりのメソッドに"初期状態"を渡してやれば良い。
「100円入れて、10円入れて、そのあとの投入金額合計を取得」はこうなる。



さいごに


この自動販売機のお題、オブジェクト指向で順番にやっていくと、内部状態が関わる関係でいろいろややこしい状況に陥る面白いお題になっている(やってみるとわかる)。
でもStateモナドを使ったらどうなるだろうか。僕が書いたテストコード最初から最後まで一貫してStateモナドの枠の中で収まっている(実装しておいたStateを、1そのまま、2演算子で結合、3for式で結合のどれかして、evalかけているだけ。全テストケースとも。)

モナド、面白いね!

2013年9月3日火曜日

Dispatch(reboot)で文字化けが起きた時の対応

今日これで30分ぐらい無駄にしたので忘れる前にメモ。

概要


Dispatchを使ってHTTPクライアントを作ると、稀に文字化けすることがあるので、その原因と対策をまとめる。
ちなみにDispatchはAsyncHttpClientを内部で使っていて、今回の話題はAsyncHttpClientの領域にも少し踏み込む。

原因と解決方針


文字化けの原因はHTTPクライアントにはよくある話で、HTTPレスポンスヘッダのContent-Typeにcharsetがセットされていない(または間違っている)のが原因。
Bodyバイト列をStringに変換するときにContent-Typeのcharsetを用いるのだが、デフォルトではISO-8859-1が使われるため、charset未設定のヘッダが来ると、bodyの文字コードが例えUTF8であっても化けてしまう。

で、これの解決方法として、ResponseFilterを使う。
ResponseFilterはAsyncHttpClient側の機能で、bodyの解析前に処理を挟むことができる。これを用いて、ヘッダのContent-Typeにcharsetを書き込んでしまえば良い。

DispatchからResponseFilterを挟む


あとは実際にコードを書くだけだが、今回はDispatchからの利用なのでDispatchのAPIからResponseFilterを挟む必要がある。実際にコードを見たほうが早いだろう。



Dispatchで提供されるデフォルトのHttpはconfigureメソッドを持っており、これにAsyncHttpClientConfig.Builderをいじる関数を渡してやることで、HTTPクライアントの挙動を制御することができる。今回はaddResponseFilterを使ってResponseFilterを挟んでやろうと言うわけだ。ResponseFilterの実装はcharset無しのContent-Typeが渡ってきたらcharset付きで上書きしてやろうというもの。Scalaの感覚で「Filter」というとFilterContextを受け取って新しいFilterContextを生成する実装をしたくなるが、ここでは単純にmutableなオブジェクトの操作でOK。

この例では"text/html"の時だけUTF-8を指定しているだけだが、必要に応じて実装を書き換えてやれば良い。

参考


Http.configureについてはこちら、AsyncHttpClientConfig.Builderについてはこちら、ResponseFilterについてはこちらを参照。

2013年8月10日土曜日

TDDBC参加レポート

TDD Boot Camp Tokyo 2013-07に参加して来ました。

「おい7月の話じゃねーか」と言いたくなりますがぐっと堪えて参加レポートでも書こうと思います。

基調講演


基調講演のまとめはめんd僕が改めて書くよりも素晴らしいまとめをされている方がいらっしゃるので、いくつか紹介させて頂いて終わろうと思いますw

http://makopi23.blog.fc2.com/blog-entry-96.html
http://taichiw.hatenablog.com/entry/2013/07/29/003642

ペアプロ大会


やっぱりTDDBCのいいところは、発表聞いて終わりじゃなくて、実践までがセットになっているところだなーと改めて思いました。

僕のペアもなかなか変わったペアだったので、この記事もペアプロの話メインにしようかと。
ちなみに、今回のお題は「飲み物自動販売機 Ver 2.0」でした。

で、改めてtogetter見なおしてみたんだけど、


なんか基調講演始まる前から実はもう予定調和っぽくなってるねw


というわけで、@razonさんとScalaでペアプロさせていただきました。@razonさんありがとうございます!

で、当日書いたコードがこちら。

https://github.com/shizone/tddbc201307

今回の記事ではこのコードの解説でもしてみようと思います。

「やっぱオブジェクト指向はダメだね」


というセリフが出たシーンがこのペアプロのハイライトでした。

Scalaでやるということで、コーディングスタイルとしてオブジェクト指向に寄るか関数型に寄るか選べるわけです。で最初僕らはオブジェクト指向の方を選びました。
最初のうちはうまく行ってたのですが、在庫管理を始めたあたりからテスト書くのに「モック必要じゃね」とか「他の機能もいくつか先に実装しないとテスト書けないよね」とかいうオブジェクト指向TDDあるあるにハマり始めて、イライラしてきました。

で、「よし、やっぱり関数で行こう」となったわけです。

状態(内部変数)の排除


まずはVendingMachineという型をつくります。1行だけです。



「class」という名前がついているので紛らわしいですが、これはオブジェクト指向でいうクラスとは全く違うものです。
「total」や「stock」という変数は再代入不可能になるので、VendingMachineはimmutableであり*1、状態を持ちません。
つまり型VendingMachineは現実世界のオブジェクトのテンプレートとしてのクラスではなく、ある瞬間の自販機の状態を表現するスナップショットとしての単なるデータにすぎません。

処理を関数で記述


オブジェクト指向の場合は、例えば「10円を入れる」という処理はVendingMachineクラスのメソッドとして表現し、内部変数を書き換えるという実装をするわけですが、関数型の場合は、処理を行う前のスナップショットから処理を行った後のスナップショットを計算する関数を記述します。



もちろん「関数を返す関数」を作ることもできるので、「コインを入れる」という関数はこのような表現が可能です。



関数のテストを書く


ちょっとその前に、テストを書きやすくする補助関数も作っておきます。
初期状態を取得するinitと、パイプライン演算子*2を。



パイプライン演算子の実装の方はちょっと高度なので今回は詳しく書きませんが、よく見るパターンです。Scala2.10で入った新しい機能、implicit classとvalue classを使って、メソッド「|>」を記述しています。
意味合い的にはただ単に関数適用の記述を楽にするだけのものです。VendingMachine型を引数とする関数fを、値vmに適用する際に、普通は「f(vm)」と書くところを、「vm |> f」と書けるようにするだけ。「VendingMachine => VendingMachine」という型の関数を連続で適用していく場合に、普通なら「h(g(f(vm)))」となるところが「vm |> f |> g |> h」となって素敵です。

さて、ここまで準備すると、テストコードがどうなるか。

「初期状態の自動販売機に100円玉を入れて、次に10円玉をいれると、投入金額合計は110円になる」というテスト、こうなります。
(当日はScalaTestを使いましたが、僕はSpecs2の方が慣れてるのでSpecs2式に書きます)



まとめ


「TDDBC当日のペアプロ大会で、Scala組はこんな感じのコードを書いてました」という紹介をしてみました。
一部Scala黒魔術が顔を見せたりしてはいるものの、簡潔で読みやすいテストコードが書けるよーという様子を感じて頂ければと思います。

実は「関数型はUnit Testと相性がいい」という僕の持論もあるのですが、その話はまた今度。



*1: ちなみにScalaではMap型も(特にmutableなものを使うことを明示しない限り)immutableです。
*2: パイプライン演算子はもともと「F#」由来の素敵な演算子です。ちょうどいいページが見つからなかったのでテキトーにググってくださいwちなみにScalazに実装されています。

2013年8月7日水曜日

Gitについて書いておく

※ 本記事の内容は「僕の個人的な考え」であって、正解とは限りません。予めご了承ください。あと、長いです。

能書き


ここ半年ぐらい、僕のまわりでは「Gitに移行しようぜー」的な波が押し寄せてきてる。前職の職場も、今の職場も、大学時代の友達も。一方僕は、学生時代(3年前ぐらい?)にたまたま偶然Gitに首突っ込んだので、もう既にGitがないと生きていけない体になってしまっているという立場。ということで、Gitについて思うところを僕なりに一回文章にしてみようと思う。
ここに書くのはGit入門ではなく、「Gitの基本コマンドは覚えて使えるけど、どうやったらGitっぽくつかえるか、Gitの恩恵を得られるか掴みきれてない」ぐらいの習熟度の方に参考になるような内容の予定。もちろんここに書いてあることに従えと言いたいわけではないので、違う意見の場合はぜひコメントなど頂ければ幸いですし、Git勉強中の方は1つの意見として参考にしていただければと思う。

SVNとの違い


SVNなど、一世代(あるいはもっと)前のVCSからGitに移ってくる場合に、SVNとGitの違いという観点からGitを理解しようとすることは多いと思う。そのアプローチ自体は良いとして、1つ問題がある。SVNとGitの違いの話をするときに大抵の場合、まず最初に「中央管理型か分散型か」を挙げられてしまうことだ。もちろんそれは重要な違いではあるのだが、その違いが重要なケースは稀だ。Linuxプロジェクトのように(開発者の人数という意味で)大規模なプロジェクトの管理をする立場になったら考えることであって、一開発者の立場なら、「へー」ぐらいの理解から始めれば十分だ。(もちろんこれから挙げていくGitの特徴の根本にあるのは「分散型」の恩恵なので、深堀りするときには勉強しよう)

というまえがきを置いた上で、僕はSVNとGitとの違いとして、「ブランチの粒度の大きさ」を挙げたい。

Gitでは、とにかくブランチを細かくたくさん切るのだ。たった1行の修正のために1つのブランチを切るなんてことも決して珍しくはない。
一例として、今僕は会社でソロ開発してんだけど、7/1から7/25までの1ヶ月弱の間に切ったブランチの数カウントしたら、120だった。たぶん、SVNでは想像できない数なんじゃないかな。

というわけで、これからブランチを細かく切ると何が嬉しいのかってところを掘り下げて行くことにする。

帽子をかぶり分けるためにブランチを切る


開発の世界では「帽子をかぶり分ける」という言い回しをよく使う*1のでここでも引用させてもらった。
つまり、今、機能Aの開発をしているのか、バグBを直しているのか、処理Cのパフォーマンス向上のための改修をしているのか。どの作業をやっているのか明確にした上で、そのことだけを考える。そのためにブランチを切るのである。

例えば、開発してると、以下の様な場面に出くわすことがあるだろう。

  • 機能Aの開発中に、機能Aとは関係ないバグBを見つけた
  • 機能Cを作っていたが、機能Dを先に作っておく必要があることに気づいた
  • コメントが気に食わない、タイプミスが気になる、リファクタリングしたい、等々

このような場面でそのまま本能の赴くままに手を加えてはいけない。あれもこれも一緒に実装しようとすると、だいたい何か考慮漏れがあって、バグる。そこで、「このブランチではこの作業だけをやる」というルールを自分に言い聞かせて、頑なに守るのだ。ブランチの切り替え(git checkout)が帽子のかぶり換え。定量的にはわかりにくいかもしれないが、帽子のかぶり分けの効果は、スピードや品質の向上にかなり有効だと思う。
ちなみに、「今どの帽子をかぶってるか=今どのブランチにいるか」がすぐわかるようになってると、脳の切り替えには有効だ。なので、手慣れたシェルがあれば、是非、プロンプトに「今いるブランチ名」を表示するように設定しておこう。
また、あとで少し触れるが、マージ作業や、後から変更履歴を確認するときなんかにも、「機能Aのブランチでは必ず機能Aの実装だけが行われている」ことはとても有効だ。

"じゃあ、「今のブランチとは関係ないバグB」や「先に作らなきゃいけない機能D」はどうするの?"
答えはこう。

  1. issue tracking systemにissue切る(まずは何も考えずにissue切る。これやらないと、忘れるよ。)
  2. 関係ないバグBは放置。あとでやろう。
  3. 先に作らなきゃいけない機能Dが見つかったら、今のブランチのことはひとまず忘れて、機能D用ブランチを切ろう。


できる限り狭い範囲だけを考えればいいように影響範囲を限定しながらソースコードに手を加えるのは、開発の基本。ブランチの切り方も、この考え方にきっちり従っていこう。

粒度の話


ここまでの話をより深くイメージするには、「機能A」ってのはどれくらいの粒度なのよってのがあると良いと思うので、ここで触れておこう。

僕は1つのfeatureブランチは、だいたい以下の様な大きさに収まるように、という方針で切っている。(もちろん状況によるので必ずしも全てのブランチがそうなっているということはないが。)

ブランチ単位のdiffをとったとき、出てくる各diffが何を意図して行われた変更か、すぐに想像できる程度

ソースレビューなんかを実施する文化で開発したことのある方だと想像しやすいと思うが、ちょっとdiffの量が増えると、すぐ「必死にdiffを読み込まないと意図が把握できない」という状況になってしまう*2。そうならない程細かくブランチを切るのだ。

なぜそこまで細かくするか。

繰り返しになるが、それだけ「考える範囲を限定する」ことによってスピードと品質の高い開発をしようというのが一点。

そしてもうひとつは、「マージのため」という視点だ。VCSの利用において、一番事故につながりやすい作業が「マージ」だ。できるだけマージはしたくないという意図はわかるが、Gitの考え方は逆方向で、「マージの回数は増えても、安全なマージをする」だ。まず単純に、diffが少なければ競合しにくい。マージってのはブランチ単位でするものだから、ブランチが小さければ小さいほど競合しにくいという至極単純な理屈。
しかし競合は起きるものであり、いかに競合を正しく解決するかも考えなければいけない。正しく競合解決するために必要なものは「コード変更の意図を正しく把握すること」だと僕は思っている。ブランチの規模は「diffを見て意図をすぐ読み取れる」程度にしておくと、競合した時にお互いの変更をどのように組み合わせるのが正しいか把握しやすい。この辺りは文章では伝わりにくいかもしれないので、是非手を動かして実感して欲しい。


CIとの相性


あともう一つ、ブランチを細かく切るということは、CI(継続的インテグレーション)との相性が良いということにも言及しておきたい。

ビルドを回すには、最新のコードが「動くもの」になっている必要があるが、複数featureが混ざった長いブランチで開発していると、「動く状態」のタイミングが訪れにくい。「機能Aの実装は終わったけど機能Bが中途半端に実装開始している状態」コミットとかね。そうなると、結局全機能実装し終わるまでテストを回すことができなくなって、それって結局CIでもなんでもないということになる。

それに対して、あくまで最小単位の機能でブランチを切り、その機能だけが新たに動く状態になった時点でマージするというのがGitのスタイルになる。マージ先になるブランチには、機能が出来上がるたびに、プロダクトが動く状態でマージされていくので、このブランチはCIツールに監視させるブランチにピッタリだ。
(A successful Git branching modelをイメージして書いてます。これで言う、「develop」ブランチは、CIのためにあると僕は思っている。)

なんかGitの使い方というより、ブランチの切り方の話してない?


仰るとおり。

あくまでブランチの切り方が大事だと思っているのは確か。そして、ツールの機能としてはSVNでもこのブランチの切り方を実践することはできるだろう。だけど、このブランチの切り方を実践するにはGitじゃないとダメなんだ、これが。
なぜなら、この開発スタイルになってくると、ものすごい頻度でブランチ切るし、diff見るし、マージすることになる。SVNだとそれらのコマンドを打つたびにリポジトリサーバと通信が走り、待たされる。でもGitならこれらのコマンドはすべてローカルリポジトリで完結するから、一瞬で結果が表示される。だから分散リポジトリ型のVCSが、Gitがイイ。

一度Gitに慣れてしまってから「もうSVNには戻れない」って言う人は、そのサーバと通信してる数秒×何千回の時間浪費に戻れないという面が大きいのだと思う。

まとめ


というわけで何が言いたかったかというと、僕は別に「Gitのこの機能が良い!」という風には全然考えてない。「CVSからSVNに移行したときのように、SVNからGitに移行した時も打つコマンドが変わるだけ」というGitの使い方では意味が無いのだ。
では何がすごいのか。それは、「ちっちゃいブランチを切る」ことから始まる新しい開発スタイル*3だと僕は思っている。



*1: TDDでは「テストを書いてコンパイルを通す」「テストをグリーンにする」「リファクタリング」のステップがあり、明確に作業を分離する。例えば「コンパイルを通す」のステップで実装(テストを通す)を考えてはいけない。このように今どの作業をしているのか明確にし、その作業のことだけを考えるというスタイルを「帽子をかぶり分ける」と表現する。
*2: 「そして形式だけのソースレビューが行われて、レビューの効果が出るわけもなく、バグる」までが予定調和。
*3: ゆーてもGit自体そこまで新しいというわけではないので「新しい開発スタイル」は言いすぎかもしれないが、少なくともSVNベースとは違う開発スタイルになる。