読者です 読者をやめる 読者になる 読者になる

#tddbc 横浜でLTとTAっぽいことをしてきました

イベント TDD

TDDBC横浜にLT枠で参加&JavaのTAもどきをやっていました。

2011/11/05 TDD Boot Camp 横浜 #tddbc - Togetterまとめ

全体的な内容を見たい場合は@さんのブログへいきましょう。
TDD Boot Camp 横浜に参加してきた #tddbc - Shinya’s Daily Report

LTについて

LT資料をはりつけておきます。

ここでgit-nowについて補足。
資料では「git-nowを使うなら対話的rebase必須」と書きましたが、という指摘をいただきました。確かにこの--fixupを使えば対話的rebaseは必須というわけではなくなりますね。@さん、ご指摘ありがとうございました。


研究データは修士論文の行く末にもかかわってくるので、ご協力いただければ幸いです。

演習に関して

今回は野球の打率計算やランキングに関してのお題。
開催1週間ほど前にお題のプレビュー版が公開されていたので、F#,Groovyでフライング演習しておきました。
Groovyに関しては当日ペアができあがっていたのでそちらにおまかせするとして、ここではF#+NaturalSpecのコードを公開しておきます。
お題は下記リンクにて公開されております(当日はお題3までが範囲でした)。
TDD Boot Camp(TDDBC) - TDDBC横浜/課題
課題レビュー用に作ったものなので漏れがあると思います。F#erな方はおかしなところを見つけたらご指摘ください。

(* テストコード *)
module BattingAverageScenario

open NaturalSpec
open BaseBall

module 打率 =

  module 計算 =

    [<Scenario>]
    let ``打席数が0なら打率はNone`` () =
      Given (0, 0, 0)
      |||> When BattingAverage.calc
      |> It should equal None
      |> Verify

    let CalcTestCases =
      TestWith (MultiParam [|2;2;1;0.500M|])
        |> And (MultiParam [|6;6;1;0.167M|])
        |> And (MultiParam [|1;0;0;0.000M|])

    [<ScenarioSource "CalcTestCases">]
    let ``打席数,打数,安打から打率を計算できる`` box atbats hits avg =
      Given (box, atbats, hits)
      |||> When BattingAverage.calc
      |> It should equal (Some avg)
      |> Verify

  module 出力 =

    [<Scenario>]
    let ``計算されていない打率の出力は"----"`` () =
      Given None
      |> When BattingAverage.print
      |> It should equal "----"
      |> Verify

    let PrintTestCases =
      TestWith (doubleParam 0.500M ".500")
        |> And (doubleParam 0.167M ".167")
        |> And (doubleParam 1.000M "1.00")

    [<ScenarioSource "PrintTestCases">]
    let ``打率avgを出力できる`` avg result =
      Given (Some avg)
      |> When BattingAverage.print
      |> It should equal result
      |> Verify

  module ランキング =

    [<Scenario>]
    let ``打率の高いほうがランキング上位`` () =
      let alice = {Name="alice";Box=1;AtBats=1;Hits=1}
      let bob = {Name="bob";Box=2;AtBats=1;Hits=1}
    
      Given [bob; alice]
      |> BattingAverage.ranking
      |> It should equal [alice;bob]
      |> Verify

    [<Scenario>]
    let ``打席数が0の選手と打率が0割の打者では、打率0割の打者のほうが上位`` () =
      let alice = {Name="alice";Box=1;AtBats=1;Hits=0}
      let bob = {Name="bob";Box=0;AtBats=0;Hits=0}
    
      Given [bob; alice]
      |> BattingAverage.ranking
      |> It should equal [alice;bob]
      |> Verify

    [<Scenario>]
    let ``打席数が0の選手と打率が0割の打者と打率が5割の選手では、打率5割,0割,打席数0の順序`` () =
      let alice = {Name="alice";Box=1;AtBats=1;Hits=0}
      let bob = {Name="bob";Box=0;AtBats=0;Hits=0}
      let mike = {Name="mike";Box=4;AtBats=4;Hits=2}
    
      Given [bob; alice; mike]
      |> BattingAverage.ranking
      |> It should equal [mike;alice;bob]
      |> Verify
(* 実装コード *)
module BaseBall

open System

type Player = {
  Name:string
  Box:int
  AtBats:int
  Hits:int
}

module BattingAverage =

  let format = 3

  let calc box atbats hits =
    match box, decimal atbats, decimal hits with
    | 0 , _ , _ -> None
    | _ , 0M , _ -> Some 0.000M
    | _ , atbatsm , hitsm -> Some <| Decimal.Round(hitsm / atbatsm, format)

  let print = function
    | None -> "----"
    | Some 1.000M -> "1.00"
    | Some avg -> avg.ToString(".000")
  
  let ranking players =
    players |> List.sortBy (fun player -> calc player.Box player.AtBats player.Hits) |> List.rev

F#では値があるかないかにOptionを使い、値があることをSome x、値がないことをNoneで表現します。Optionとパターンマッチを併用することで、もしNoneに関する処理を実装し忘れていてもコンパイラが警告をだしてくれたり、Option用モジュールが提供されていたりと、nullと違って非常に便利な代物です。


打率計算calcでは、パターンマッチを使って処理を分けています。順に見ていきましょう。

  • match box, decimal atbats, hits with

matchとwithの間にパターンマッチさせたいデータを記述します。
ここではint*decimal*decimalのタプルを対象にしています。decimal hogehogeをdecimalに変換しています。

  • | 0,_,_ -> None

打席数が0にマッチしたらNoneを返します。この場合、打数と安打数は処理に関係ないので_を用いて任意の値にマッチさせています。

  • | _,0M,_ -> Some 0.000M

打席数が0の場合は先ほど処理を書いたので、次は打数が0の場合の処理を書きましょう。
打数が0であればSome 0.000Mを返しています。ここでは打席数と安打数が処理に関係ないので_にしています。なお、パターンマッチは上から順に評価されるので、この条件に到達するデータは打席数が必ず0以外のデータです。
0.000Mはdecimalのリテラル表現です。

  • | _ , atbatsm , hitsm -> Some <| Decimal.Round(hitsm / atbatsm, format)

上記二つの条件にマッチしなければ、この条件にマッチします。
先ほどとは違い、打数と安打数は打率計算に必要なので、atbatsm,hitsmに打数,安打数を束縛させています。
安打数を打数で割って打率を計算し、Decimal.Roundを使って桁処理を行い、それをSomeで包み込んで返すようにしました。
引数についての判断ですが、どうしようか迷いつつ「タプルは要素3つまでならOKだから今回はまだいいかな」ということで3つのまま実装しました。JavaやGroovyなら迷わずクラスを作ったと思うのですが…さてはて。
無効値をテストしていないのは…言い訳はしません、精進します。


次に打率表示printですが、この関数は打率を引数にとりパターンマッチを行っています。
Noneの場合は打率が計算されていないので"----"、打率が10割(つまり1.000M)であれば"1.00"、それ以外の値は".xxx"に整形した文字列を返します。


ランキング取得rankingでは、Playerのリストに対し打率をキーとしてソートを行っています。
Optionの比較はNoneのほうがSome xよりも小さいと判定されます。また、List.sortByは小さい順でリストを取得するので、List.revで要素の順序を反転させることで仕様を満たすデータの順序にしています。


テストコードの[]部分がParametarized Testになります。ただ、本当は外部ファイルから読み込むような形式にしたほうがいいと思うので、練習のとき以外でこの書き方をすることはあまりないのではないでしょうか。
本当は無効値処理も実装すべきだろうなと思いつつ、ちょっと気力がないのであります…命名規則もなんとかしないと。

感想

箇条書き形式で列挙します。

  • もって行くつもりだった「プログラミングGroovy」を荷物に入れ忘れたため、行きがけに布教用として調達した
  • 一つのTDDBCに3名のTDDBC主催者がいるのは珍しいような
  • 今回は初参加の方が多かった印象
  • 需要の減らない関東圏TDDBC、さてはて…
  • t-wadaさんの講演はいつも通りの安定した素晴らしさだった
  • ペアプロデモのやり取りでほんわか
  • LTは研究の話のみするつもりだったが、それもどうかなと思いGitの話をいれてみた
  • JavaのTAなのにあまりサポートできなていかった…すみません
  • Groovyに急遽参加する方がいてこれは嬉しかった(Groovy初挑戦でnobeansさんとペアプロ…羨ましい!)
  • F#でレビューに参加させてもらえた。わがままを聞き入れてくださったせとさん、ありがとうございました。
  • Groovy&Spock!
  • 懇親会では就職話を色々聞けたような(闇も聞けたような…)
  • 懇親会中にTDDBC福岡2の打ち合わせに付き合ってくださったt-wadaさん、ありがとうございます。良いイベントにできるようにがんばります!
  • JUnitはCategoryやTheory、assumeなど色々な機能があるので使いこなせるようになりたい
  • 2次会にも参加してみた
  • 「髪をレッドとグリーンに染めて『俺がTDDだ!』と言うとか」というよくわからない会話ががが
  • "こんな○○さん見たくなかった"の真相は闇の中
  • 言語数が増えるほど運営も大変になるので、このあたり何とかできないかな…

おわりに

TDDBC横浜の主催者である@さん、@さん、スタッフの皆様、参加者の皆様、ありがとうございました。やはり他人のコードや他言語の実装を見るのは勉強になりますね。
さて、今月はTDDBC福岡2も予定されております。参加枠はまだまだ余っているのでぜひご参加ください!
TDD Boot Camp 福岡 2 Day 1 : ATND
TDD Boot Camp 福岡 2 Day 2 : ATND
また、スタッフの募集も続けておりますので、お手伝いいただける方はご連絡いただければ幸いです。