日々常々

ふつうのプログラマがあたりまえにしたいこと。

思い通りに動くコードを書きたい #TddAdventJp

2012年のTDD Advent Calendar、4日目でございます。

自分にとってのTDDを考える

「TDDとは?」なんて掲げたところで、万人に通じる明確な答えを私は持っていません。原典はありますが、相応に進化も派生もしておりますので、固執する必要はないと思います。その上であえて「私にとってのTDD」を挙げるなら、以下の二点になります。

  • 思い通りに動くコードを書く
  • コードの成長を邪魔しない
思い通りに動くコードを書く

「コードは思ったとおりに動かず、書いたとおりに動く」と言う格言があります。みんな聞いた事も体感した事もあるでしょう。至極当たり前の事と思われているかもしれませんが、これを素直に受け入れてしまうのはなんだか癪です。

もし、思い通りに動くコードを書けたらどうでしょう。自分の言った事と実際の挙動に違いがあれば、件の格言をドヤ顔で言われておしまいですが、それはとても悔しい。なので私は、私の精神の安定のためにも、思い通りに動くコードが書きたいです。

思い通りに動くコードを書くための方法は、思いと動きが一致しているかを確認すればいいはずです。つまり、自分が書いたコードを動かして、動きが思った通りであるか見てみるんです。とても当たり前の話ですね。書いたコードが思った通りに動く事に自信が持つ方法は、動かした結果が思った通りであるかを確認することになります。


なんだか面倒に感じますよね?感じません?だってこのプロセスってDRYじゃないじゃないですか。

  • 考える→コード書く→実行して結果を見る→考える(思った通りだろうか?)→できた


ほら、2回考えなきゃいけない。実行した結果が思った通りであるかを考えるとかやってられません。DRYじゃないので、一回目に考えたときと二回目に考えたときで違うものが出てきたりします。他人と昔のことについて話すとやりがちな「言った言わない問題」のようなことを自分の中でもやるのは嫌です。なのでこうなります。

  • 考える→テスト書く→通るまでコード書く→できた


考えて出てきた思いをテストに押し付け、その通り動くコード以外は「できた」に到達しません。

二度考えない事のメリットは二点。一点目は、現実を見ずに考えた理想を押し付けること。理想大事。具体的に言うと、フラグ引数のような「実装の手が抜けそうな微妙なコード」が生まれ難くなります。もちろん、現実に即しないユメミガチな理想は修正されますが。二点目が、DRYであること。二度考えると、二度目の考えの方が新しく具体的なのが普通です。そして、そこで入った異なる考えを一度目にも反映する必要があるのですが、しばしば漏れたり誤魔化されたりします。最初に思い描いたことは有耶無耶になり、どこかに消え去ってしまうかもしれません。折角考えたのに。考える箇所を一カ所にすることで、二度考えることで起こりそうなズレが起こりません。

プロセスのステップが増えると、それだけノイズが入りやすくなります。なるべくシンプルに保つ事で誤りの入り込む余地を減らすのはごく当たり前のアプローチで、それを適用しているに過ぎません。必要なのは問題に対して「考える」ことと「できた」結果。プロセスをリファクタリングし、思いと動きを近づける事で「思い通りに動くコード」を得やすくなります。なってたらいいな。

コードの成長を邪魔しない

生き物では当たり前の事ですが、コードは勝手に良くなろうとします。「良くなる」と言うか「あるべき姿」と言うか、上手い表現が思いつきませんが、そういうのに寄ろうとする力があります。コードは自らを変更する事が出来ませんが、そうあるようにプログラマに対して促します。コードの求めに応じれば、本来あるべき姿によっていくものです。シンプルな例を挙げると、「不適切な変数名を見ると直したくなる」などがこれにあたります。

一方で、開発者は変更を恐れます。「動くものは触るな」と言う格言がありますが、これは触った後に動かなくなる事を恐れるためです。動かなくなる、つまり思った通りに動かなくなる。これが起こるのは、思った通り動く状態を維持する支えがないからでしょう。シンプルな解答は「変更しない事」であり、ガチガチに縛ることで「思った通りに動く状態」を維持しようとします。結果、動きは維持されるかも知れませんが、コードは成長できなくなります。もう一つの解答が、「思い通りに動いている事を確認し続ける」ことであり、それを実現するためにテストコードを使用します。この二つのアプローチは、ともに「思い通りに動く事」を維持しようとしますが、前者に動きが維持されていることの保証はどこにもなく、後者は確認できている以外の挙動には何の保証もありません。どちらが適切かはケースバイケースになるかもしれませんが、結合を粗に保ち、影響範囲を制御できれば、全ての挙動を確認することも不可能では無くなります。

ひとたびコードを「変更していいもの」に出来ると、自然な成長による自浄作用が起こります。割れた窓を修復して良くなるので、ボーイスカウトルールの適用範囲外も掃除できるようになります。仕事は厳しいスケジュールに追われる事もありますが、常にそう言うものでもありません。波はありますので、ふとした時に技術的負債を返却できるようになります。綺麗な所では気持ちよく過ごせるので、私の精神的安定にも貢献する事になります。
「そう出来るところはいいよねー」なんて声が聞こえる気もしますが、それには異を唱えたいところです。多くの事にも通じますが、TDDの適用範囲はマトリーショカのようになっているので、出来る範囲だけでもやればいいんです。少し前に自分が書いたコードに対し、「こないだ動きを確認した部分だから触りたくない」なんて思ったことはあると思います。その部分を変更出来ないのが自分の心の中だけの話だったならば、自分一人だけでのTDDであっても、その変更できない理由を取り除く事が出来ます。
折角コードに良くなろうとする力があるんですから、生かしましょう。変更出来ない理由を探すのは、変更しないためではなく、変更できるようにするためです。やらないための出来ない理由探しは簡単ですが、そんなものに価値はありません。やるために、出来ない理由を探しましょう。


以上二点が私がTDDを選択するです。他に様々な、挙げるのすら面倒になる程のメリットはありますが、私にとっては副次的なものです。これらの目標が達成できるのであれば、TDDに固執するつもりはありませんし、場合によっては崩す事もあります。あくまで道具としての選択、向いているパターンがあったので適用している感じです。

その一方で

色々と述べましたが、「思い通りに動くコード」を書けるようになったとしても、自分の認識の外で何が起こるかはわかりません。認識の外なんですから当たりまえです。その問題がある事を理解した上で、対処する必要があります。万能の道具なんてないので、特定の道具を選択すると言うことは、その弱点を他で補うと言うことでもあります。
挙げられる対策は二点。一つは認識の外を無くす。もう一つは認識の外で起こる事に備える。意味が分かりませんね。

認識の外を無くす

認識可能な範囲であるにも拘らず、漏れてしまう事はしばしばあります。それ自体を責めるのは人間をやめる行為なので論外ですが、なるべく減らしてゼロに近づける事は出来ます。
TDDの場合、これに対するアプローチの一つは、既に先に挙げたように影響範囲を局所化して閉じ込め、実験室のような完全に制御できる環境を作り上げることです。もう一つは、テスト技法を活用することです。具体的な技法を挙げるのは、私自身が勉強中なこともあり、誤った先入観や誤解もたれるのは拙いので避けますが、「思った通り」になるパターンの抜け漏れを減らすのに非常に役立っています。実際、認識の外にあったものを見つけて「あ……」と口に出してしまうこともしばしば。嫌な予感がした時に書いてみたりする程度ではありますが、知ってて良かったと幾度も思わされました。逆に知らなかったり、使いこなせてない故に漏らしているものが残っているのも事実ではありますが、それでも有効なことにかわりはありません。
「認識の外」は本当に範囲外であるものと、範囲内であるにもかかわらず、盲点のように抜けている部分の二種類があります。外の範囲をなるべく狭め、内側の穴をなるべく減らすことで、出来る限り認識の外を無くしていきます。

認識の外で起こることに備える

完璧なんてものはありません。起こらないことになっているのは自分の脳内だけの話です。この場合、認識の外で起こることに備える必要があります。
自分の思った通りに動くものが出来た。ここまでは良い。でもそれが自分の思った通りにしか使われないかは別の話で、それも含めて自分の認識の支配下に置くのは現実的ではありません。少なくとも私の脳みそではそこまで賄うことは出来なさそうです。ならば、認識の外で何かが起こることを受け入れることになります。
範囲の外があることを受け入れれば、それに対処が必要なことがわかります。私のTDDが賄える範囲は、どこまで行っても「私が思った通りに動くソフトウェアを作る」ところまでです。目的は何かを見据えた上で、例えば価値のあるソフトウェアのために他の視点が必要なら、それらを使うことを躊躇いません。実際に画面をポチポチ叩いて今までやっていたような手動テストも行うのはそれであり、カバーできる範囲が異なる以上は、それをしない理由にはならないのです。
ただ、被る範囲があるのも事実なので、少なくとも被っている範囲では自分の思い通りに動くはずなので、そこで何かが起こるってことは極端に減らすことが出来ます。これは直接的に時間短縮に繋がり、被っていない部分に注力できる結果となります。出来るならば、被っている範囲はDRYじゃないので削っていきたいものですね。

まとめ

誰得なのかは気にせず書殴った感じなので、まとめるようなことも無いのですが……
私にとってTDDは、開発プロセスリファクタリングしたらそれっぽくなっていったので、沿っている感じです。なんて言うか、しっくり来て、楽だった。やることはなるべくシンプルに保ちたいし、考えたものは最後まで繋がって欲しい。折角考えたことが置き去りになるなんて、勿体ないことはしたくないんです。
道具を使う上で重要なのは、何をしたくてそれを手に取るかだと思います。そして手にすることを決めたならば、出来ることの向き不向きを見極めること、道具の扱いに慣れること、そして道具自体を磨くことを意識していく。使わない道具は役に立ちませんし、使わなければ道具を使えるようになんてなりません。ちょっとしたことでも、素振りでも、何でも構わないので、自分の目的に向いている道具だと思ったならば、とにかく使ってみるのが良いんじゃないかなと、自戒を込めて。