亀岡的プログラマ日記

京都のベッドタウン、亀岡よりだらだらとお送りいたします。

TDDで作るBrainFuck実行機 5(最終回):リファクタリングをしないとTDDとは言えんのですよ。

なんとなくインデックス作っておきます。

リファクタリングをするかしないかがTDDかどうかの分かれ道

みたいなことがTDDer(なんて読むんだこれ)では常識らしいですね。まぁ僕も最近はそう思い始めてます。TDDの利点は安心してコードがいじれること。だとすると書いたまんまいじらないで放置してしまっては、一体何のためのテストコードなんだか、ということになってしまいます。

ということで、リファクタリングです。

括弧の位置くらい覚えておけばいいんじゃないの??

さて、どこをリファクタリングしましょうか。とはいえもう明らかに無駄なコードがあります。括弧の探索ですね。BrainFuckにおける括弧はループのためのみに存在しています。ということは、イコール何度も繰り返すということ。それなら、辞書にでも放り込んでおきましょう。というわけで、括弧の位置を見つける関数を呼ぶ前に、辞書を探索するようにしました。

		// 入力を括弧位置のインデックスとする
		int GetIndexOfEndBrucket(int currentIndex)
		{
			if(!_brucketPairs.ContainsKey(currentIndex))
			{
				_brucketPairs.Add(currentIndex, FindIndexOfEndBrucket(currentIndex));
			}
			return _brucketPairs[currentIndex];
		}
		
		int FindIndexOfEndBrucket(int index)
		{
			//まずひとつ進めて・・・
			index++;
			//括弧閉じのカウンタも1に初期化
			int brucketCounter = 1;
			//括弧閉じカウンタが0になるまで入力文字をパース
			while(brucketCounter > 0)
			{
				if(index >= _input.Length)
				{
					throw new ApplicationException("括弧が閉じられていません");
				}
				
				//開き括弧なら括弧カウンタを加算
				if(_input[index] == '[')
				{
					brucketCounter++;
				}
				//閉じ括弧なら括弧カウンタを減算
				else if(_input[index] == ']')
				{
					brucketCounter--;
				}
				index++;
			}
			//必ず一つ行き過ぎているので、一つ戻しておく
			return index - 1;

閉じ括弧も同じですね。

		//入力を括弧閉じのインデックスとする
		int GetIndexOfStartBrucket(int currentIndex)
		{
			if(!_brucketPairs.ContainsValue(currentIndex))
			{
				_brucketPairs.Add(FindIndexOfStartBrucket(currentIndex),currentIndex);
			}
			
			return _brucketPairs.First(dict => dict.Value == currentIndex).Key;
		}
		

		int FindIndexOfStartBrucket(int currentIndex)
		{
			//初期化
			currentIndex--;
			int brucketCounter = 1;
			//前に戻りながら括弧を見つける
			while(brucketCounter > 0)
			{
				if(currentIndex < 0)
				{
					throw new ApplicationException("括弧が閉じられていません");
				}
				
				if(_input[currentIndex] == '[')
				{
					brucketCounter--;
				}
				else if(_input[currentIndex] == ']')
				{
					brucketCounter++;
				}
				currentIndex--;
			}
			//必ず行き過ぎてるので戻しておく
			return currentIndex + 1;
		}

とまぁ、素直に実装したので、テストを走らせておきます。よし、OKOK。
しかしこれ、閉じ括弧から戻るときには全探索かける必要があるんですよね。。。ちょっと時間はしかも。まぁ閉じ括弧は最後だから一回だけ・・・ではなくて、むしろ開き括弧から閉じ括弧に飛ぶケースのほうがかなり稀です。というかループを抜ける一回のみです。わひゃあ。

それにまぁあり得ないかもしれませんが、閉じ括弧で抜けた時には辞書から削除しておいても問題はないはずです。バカみたいにる
ル-プさせたときは若干ながらメモリを食うし、なんとなくもう使わないものは削除しておきたいですねぇ。。。

いやいやそもそもそもSwitch文なんて邪悪な構文は腹を切って死ぬべきなんじゃないの?


、、、と。かように書いてから悶々とすることはやっぱり多いわけです。こうした時に安心してさわれるようにユニットテストを整備しておくのは重要ですね。ほんとに。

最後にシナリオテストを

といってもそんな大げさなものではなく。Wikipediaの各言語Hello Worldに乗っているBrainFuckHello Worldをテストしておきましょう。

		[Test]
		public void ハローワールドテスト()
		{
			var compiler = new BrainFuckCompiler.BrainFuckCompiler("+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.------------.<++++++++.--------.+++.------.--------.>+.");
			var answerList = new System.Collections.Generic.List<byte>();
			compiler.OutputEvent += output => {
				answerList.Add(output);
			};
			compiler.Run();
			answerList.Is(
				"Hello, world!".Select(ch => (byte)ch));
		}

f:id:posaunehm:20120221211652p:image

よし、OK!(・∀・)

なんとなくTDDやってみて感想とか。

だらだらと簡単な課題でTDDしてみました。実際問題、ひとまとまりのプログラム全体をTDDしてみるというのは初めての景観だったりします。まぁコード行数的にとんでもなく少ない課題ではありますがw
簡単なネタが思いつかず、とりあえず記事になるからやってみるかぁ、位のノリだったのですが、実際にやってみると自分の中でTDDをどう消化していくか、という指針はできたかなぁ、という気はします。

TDDはできないプログラマのためのもの

じつは会社の尊敬する先輩が、だいぶ前から「TDDは実装効率落ちるし、あんま最近はやってない」と言われていたのがずっと引っかかってはいました。でも自分でやってみて、ハタから見るとこんなに馬鹿らしい作業すら間違える自分を発見して納得した次第です。僕は現実世界でもかなりのミスりキャラで、まぁドタバタしてます。んで、今回コードを書いたら基本的なものを間違えてること間違えてること。Whileループの中で停止条件のインクリメントを忘れてたりとか、forループのインデックス変数を取り違えてるとか、もう色々。散々でした。

んで、そういう時にテストは冷酷に赤くなってくれるんですよね。もちろんデバッグの動作確認でそこら辺は分かるんですが、新機能追加によるデグレードなんかは拾いきれなかったりします。というかそもそも間違いに気づくのが遅すぎてなかなか発見出来なかったりとか、ね。とにかく、TDDやってると自分のミスを自分でカバーできます。それだけでも僕にとってはかなりのありがたみです。

んで、やっぱある程度以上出来る人はそこに時間とか手間を割きたくないんだろーなー、というのはわかります。間違えるはずのない事をわざわざ確認してるわけですし。

とりあえず、しばらくはテストコード書く癖を定常的につけようと思います。ダメな自分を認識できて、それを正す手段も分かったんだから、やんないとダメですよね。

というわけで、これを読みます。英語の勉強を兼ねてね!

The Art of Unit Testing: With Examples in .net

The Art of Unit Testing: With Examples in .net