「おい7月の話じゃねーか」と言いたくなりますがぐっと堪えて参加レポートでも書こうと思います。
基調講演
基調講演のまとめは
http://makopi23.blog.fc2.com/blog-entry-96.html
http://taichiw.hatenablog.com/entry/2013/07/29/003642
ペアプロ大会
やっぱりTDDBCのいいところは、発表聞いて終わりじゃなくて、実践までがセットになっているところだなーと改めて思いました。
僕のペアもなかなか変わったペアだったので、この記事もペアプロの話メインにしようかと。
ちなみに、今回のお題は「飲み物自動販売機 Ver 2.0」でした。
で、改めてtogetter見なおしてみたんだけど、
Java席はJVM席なのではないかというアレ #tddbc
— 食洗機パイセン (@razon) July 27, 2013
え?Java席はJVM上で動けばOK? #tddbc
— Hirokazu NISHIOKA (@nisshieeorg) July 27, 2013
Scalaいっちゃう? #tddbc
— Hirokazu NISHIOKA (@nisshieeorg) July 27, 2013
Scalaをおっぱじめる勢 #tddbc
— 食洗機パイセン (@razon) July 27, 2013
なんか基調講演始まる前から実はもう予定調和っぽくなってるねw
Scalaペアができました #tddbc
— 食洗機パイセン (@razon) July 27, 2013
Scalaチーム with @razon さん #tddbc
— Hirokazu NISHIOKA (@nisshieeorg) July 27, 2013
というわけで、@razonさんとScalaでペアプロさせていただきました。@razonさんありがとうございます!
で、当日書いたコードがこちら。
https://github.com/shizone/tddbc201307
今回の記事ではこのコードの解説でもしてみようと思います。
「やっぱオブジェクト指向はダメだね」
というセリフが出たシーンがこのペアプロのハイライトでした。
Scalaでやるということで、コーディングスタイルとしてオブジェクト指向に寄るか関数型に寄るか選べるわけです。で最初僕らはオブジェクト指向の方を選びました。
最初のうちはうまく行ってたのですが、在庫管理を始めたあたりからテスト書くのに「モック必要じゃね」とか「他の機能もいくつか先に実装しないとテスト書けないよね」とかいうオブジェクト指向TDDあるあるにハマり始めて、イライラしてきました。
で、「よし、やっぱり関数で行こう」となったわけです。
状態(内部変数)の排除
まずはVendingMachineという型をつくります。1行だけです。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
case class VendingMachine(total: Int, stock: Map[Drink, Int]) |
「class」という名前がついているので紛らわしいですが、これはオブジェクト指向でいうクラスとは全く違うものです。
「total」や「stock」という変数は再代入不可能になるので、VendingMachineはimmutableであり*1、状態を持ちません。
つまり型VendingMachineは現実世界のオブジェクトのテンプレートとしてのクラスではなく、ある瞬間の自販機の状態を表現するスナップショットとしての単なるデータにすぎません。
処理を関数で記述
オブジェクト指向の場合は、例えば「10円を入れる」という処理はVendingMachineクラスのメソッドとして表現し、内部変数を書き換えるという実装をするわけですが、関数型の場合は、処理を行う前のスナップショットから処理を行った後のスナップショットを計算する関数を記述します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// VendingMachine => VendingMachine | |
def insert10yen(vm: VendingMachine): VendingMachine = vm.copy(total = vm.total + 10) |
もちろん「関数を返す関数」を作ることもできるので、「コインを入れる」という関数はこのような表現が可能です。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Money => VendingMachine => VendingMachine | |
def insert(m: Money)(vm: VendingMachine): VendingMachine = vm.copy(total = vm.total + m.value) |
関数のテストを書く
ちょっとその前に、テストを書きやすくする補助関数も作っておきます。
初期状態を取得するinitと、パイプライン演算子*2を。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 初期状態(投入額0、在庫コーラ5個) | |
val init = VendingMachine(0, Map(Drink.coke -> 5)) | |
// パイプライン演算子 | |
implicit class RichVendingMachine(val vm: VendingMachine) extends AnyVal { | |
def |>[T] (f: VendingMachine => T): T = f(vm) | |
} |
パイプライン演算子の実装の方はちょっと高度なので今回は詳しく書きませんが、よく見るパターンです。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式に書きます)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
init |> insert(Coin100) |> insert(Coin10) |> total must equalTo(110) |
まとめ
「TDDBC当日のペアプロ大会で、Scala組はこんな感じのコードを書いてました」という紹介をしてみました。
一部Scala黒魔術が顔を見せたりしてはいるものの、簡潔で読みやすいテストコードが書けるよーという様子を感じて頂ければと思います。
実は「関数型はUnit Testと相性がいい」という僕の持論もあるのですが、その話はまた今度。
*1: ちなみにScalaではMap型も(特にmutableなものを使うことを明示しない限り)immutableです。
*2: パイプライン演算子はもともと「F#」由来の素敵な演算子です。ちょうどいいページが見つからなかったのでテキトーにググってくださいwちなみにScalazに実装されています。