第一回チキチキ日本ペアプログラミングの会java-ja支部会

Mac童貞を捨てて2日目。無謀にも勉強会(しかもペアプロw)にMacで乗り込んでみた。自分でもいまいち使いこなせていないのにこれでペアプロするのだ。当日はマカーの人と組んで、Mac文化も覚えるんだ〜〜とか目論んでいたのだけど、なんかその場になったらそんな事を考えずにペア組んでた。

和田さんのお話を聞きながら、FizzBuzzのコードを自分のEclipse上にも再現してみようと頑張っていたのだけれど、コード補完のショートカットが上手く機能せず、フォローしきれませんでした>< でも、新しいショートカットキーが色々出てきて「おおおっ」と感動するケースが多かった。(でも、WinとMacの両キーバインドを覚えようとして、混乱したw)

休憩中、喫煙室id:Yoshioriさんに「プラッチックMacなんて」とdisられたり、懇親会ではid:t_yanoさんに「来週新しいMacBook出るらしいよ」とウソを吹き込まれたりと散々でしたorz

実際にペアプロをするにあたって、「ボーリングのスコア計算」なんていうテーマを小耳に挟んだので、そのままじゃつまらないと思い、ダーツのクリケットのスコア計算にチャレンジしてみました。

前半はkenchanと、後半はid:onkとペア。TDDというのはテストコードを真剣に書く分コーディングのスピードは明らかに遅くなる訳だけど、ペアプロというのはあーだこーだ話しながら作るのでやっぱり楽しい。つまるところ、ソロTDDっつーのは結構シンドイんじゃないかなぁ、とも思った。まぁ後々のテストの価値や安心感を考えれば、やっておく価値は十分にあるんですが。ペアプロにはそのシンドさを補填する効能というのもあるんじゃないか、と感じた次第。

実際にコードを書いてみたんですが、まだ粒度細かくメソッドを組み立てていく事に慣れず、大きい単位のメソッドのテストを作っちゃって、実装クラスを作るときに「あ、こんなメソッドも必要だよね」とか言いながら、新しい実装メソッドを作ってしまう。つまりテストの前に実装を作っちゃう訳で、TDDではなくなってしまう。という罠にいっぱい引っかかりました。

あと、TDDをやっていても「どーせ後々こういう実装になるだろうから」という、ある意味「邪念」にとらわれて、「その時点では考えるべきではない拡張性を持った実装」を書いてしまいがちでした。そこはぐっと我慢して書かなきゃいけない所なんだろうけどなぁ…。むずかすぃ。

結局途中で煮え切らないままタイムアップとなってしまったんですが、とっても楽しかった。参加してよかったなぁ、と思えるイベントでした。

ATLを出る時にkenchanと話していたんですが、こうやって大人数で集まってペアプロ大会してみるのも良いけど、企画運営も大変そう。ならさ、ペアプロなんだからさ、誰か身近な人に声掛けて「明日暇? 飲み行かない?」ってノリで「明日暇? ペアプロしねぇ?w」って感じで、数人(最悪二人でもいいし)で集まってやってみるのも良いんじゃないか、と思いました*1。こういうノリであれば、ルノアールとか、そんな気軽な場所でも開催可能ですからね。このアイデア、賛同頂ける方がいらっしゃいましたら、気軽に誘ってください。喜んでお受けします^^ もしくはコメントに「気軽なペアプロ希望」って書いておいてもらえれば、こちらからも誘わせて頂きますYo!

そして懇親会。相変わらずです。高井さんがid:Ewigkeitの腹筋クラッシャーして遊んでました。つかみんな飲み食い激しすぎww 7200円ってすげぇ。5.7回で安く済んだ分が帳消しになりました。やりましたね。

まぁそんなわけで、お話を聞かせてくれたid:t-wada(今回は間違いません!w)さん、企画立案の@yamashiroさん、会場提供してくれたATLさん、ありがとうございました。

最後に。恥ずかしい出来ですが、ペアプロで作ったコードを貼付けておきます。CodeReposに上げる、なんて話もありましたが、よく考えたらクラス1つしか作っていないので、これで十分ですね。添削等頂けたらこれまた幸せです。

package jp.xet.cricket;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

import java.util.Map;
import java.util.TreeMap;

import org.junit.Test;

public class GameTest {

	private static final int SINGLE = 1;
	private static final int TRIPLE = 3;
	private static final int DOUBLE = 2;
	private static final int BULL = 50;

	private int round = 1;
	private int throwCount = 0;

	private Map<Integer, Integer> scoreBoard = new TreeMap<Integer, Integer>();
	{
		scoreBoard.put(15, 0);
		scoreBoard.put(16, 0);
		scoreBoard.put(17, 0);
		scoreBoard.put(18, 0);
		scoreBoard.put(19, 0);
		scoreBoard.put(20, 0);
		scoreBoard.put(BULL, 0);
	}

	@Test
	public void 同じ数字に3回あたっていなければマークされていない() {
		throwDarts(15, SINGLE);
		assertThat(isMarked(15), is(false));
	}

	@Test
	public void 同じ数字に3回あたったらマークされている() {
		throwDarts(15, SINGLE);
		throwDarts(15, SINGLE);
		throwDarts(15, SINGLE);
		assertThat(isMarked(15), is(true));
	}

	@Test
	public void トリプルに1回あたったらマークされている() {
		throwDarts(15, TRIPLE);
		assertThat(isMarked(15), is(true));
	}

	@Test
	public void ダブル1回ではマークされていない() throws Exception {
		throwDarts(15, DOUBLE);
		assertThat(isMarked(15), is(false));
		throwDarts(15, SINGLE);
		assertThat(isMarked(15), is(true));
		throwDarts(15, SINGLE);
		assertThat(isMarked(15), is(true));
	}

	@Test
	public void すべてマークしたらクリア() throws Exception {
		throwDarts(15, TRIPLE);
		throwDarts(16, TRIPLE);
		throwDarts(17, TRIPLE);
		throwDarts(18, TRIPLE);
		throwDarts(19, TRIPLE);
		throwDarts(20, TRIPLE);
		throwDarts(20, TRIPLE);
		throwDarts(BULL, SINGLE);
		throwDarts(BULL, SINGLE);
		assertThat(isClear(), is(false));
		throwDarts(BULL, SINGLE);

		assertThat(isClear(), is(true));
	}

	@Test
	public void ラウンド20を投げ終わったらクリア() throws Exception {
		for (int i = 0; i < (20 * 3); i++) {
			assertThat(isClear(), is(false));
			throwDarts(0, 0);
		}
		assertThat(isClear(), is(true));
	}

	@Test
	public void ダーツを3回投げる毎にラウンドが進む() throws Exception {
		assertThat(getRound(), is(1));
		throwDarts(15, SINGLE);
		assertThat(getRound(), is(1));
		throwDarts(15, SINGLE);
		assertThat(getRound(), is(1));
		throwDarts(15, SINGLE);
		assertThat(getRound(), is(2));
		throwDarts(20, DOUBLE);
		assertThat(getRound(), is(2));
		throwDarts(20, DOUBLE);
		assertThat(getRound(), is(2));
		throwDarts(20, DOUBLE);
		assertThat(getRound(), is(3));

	}

	@Test
	public void ダーツを1回投げる毎にスローカウントが1増える() throws Exception {
		assertThat(getThrowCount(), is(0));
		throwDarts(15, SINGLE);
		assertThat(getThrowCount(), is(1));
		throwDarts(15, DOUBLE);
		assertThat(getThrowCount(), is(2));
		throwDarts(15, SINGLE);
		assertThat(getThrowCount(), is(0));
	}

	@Test
	public void 最初はスコアは0() throws Exception {
		assertThat(getScore(), is(0));
	}

	@Test
	public void 同じ数字_15_に4回あたっていれば15点() throws Exception {
		assertThat(getScore(), is(0));
		throwDarts(15, TRIPLE);
		assertThat(getScore(), is(0));
		throwDarts(15, SINGLE);
		assertThat(getScore(), is(15));
		throwDarts(15, SINGLE);
		assertThat(getScore(), is(30));
	}

	private Integer getScore() {
		int totalScore = 0;
		for (Integer score : scoreBoard.keySet()) {
			if (getCount(score) > 3) {
				totalScore += score * (getCount(score) - 3);
			}

		}
		return totalScore;
	}

	private boolean isClear() {
		if (getRound() == 21 && getThrowCount() == 0) {
			return true;
		}

		for (Integer score : scoreBoard.keySet()) {
			if (isMarked(score) == false) {
				return false;
			}
		}
		return true;
	}

	private boolean isMarked(int score) {
		return getCount(score) >= 3;
	}

	private void throwDarts(int score, int coeff) {
		int count = getCount(score);
		scoreBoard.put(score, count + coeff);
		throwCount++;
		if (throwCount == 3) {
			round++;
			throwCount = 0;
		}
	}

	private int getCount(int score) {
		Integer count = scoreBoard.get(score);
		if (count == null)
			return 0;
		return count;
	}

	public int getRound() {
		return round;
	}

	public void setRound(int round) {
		this.round = round;
	}

	public int getThrowCount() {
		return throwCount;
	}

	public void setThrowCount(int throwCount) {
		this.throwCount = throwCount;
	}
}

*1:今回のイベントが要らなかったという訳じゃなく。今回はキッカケとして大きな意味を持つイベントだったと思います。