2011年2月1日火曜日

Test Double の「Double」とは

プログラマにとって「double」と言えば、まず第一に倍精度浮動小数点数のことだろうけど、Test Double の「double」はどうか。

昨今の開発で、まともにテスト駆動が成立するレベルのテストコード、つまり TestSmell が一定以下に抑制された DeveloperTest を書いていくためには、かなり多くの場面で Test Double パターンが必要になる。今日びのプログラマにとっては、Stub、Mock、Fake、Spy 等を含む Test Double と言われる一群のテストパターンを自在に使いこなす事は、必須技術の一つと言えるんじゃないかと思う。

個人的な話になるが、実は自分は、この Double の意味を知らないまま、長い間「Test Double」という言葉を使っていた。深く考える訳でもなく、何となく「Test」を動詞、「Double」を副詞と認識して「二重にテストする」なんて意味かなと思っていたが、TestDouble が意味する手法と一致しなくて、ずっと気持ち悪かった。

ある時、何気なくネットの辞書を引いてみたら、ちゃんと答えがあった。

Weblio の英和辞書ではこんな感じ
【名詞】3.b【映画】
代役,替え玉 〔for〕.

英英辞書の OALD ではこんな感じ
NOUN PERSON/THING [countable] 
an actor who replaces another actor in a film/movie
to do dangerous or other special things

まず、Double を副詞と考えていたのが、そもそも間違いで、本当は映画用語の名詞。Double が副詞でないということは「Test」は動詞ではありえず、したがって「Test Double」で名詞+名詞の複合名詞、つまり「テストの替え玉」という意味になる。

やっぱ辞書はこまめに引かないといかんな。

※OALD は 非英語ネイティブ向けの英英辞書としては、かなり良い感じ。ネット版だけじゃなく、リアルの辞書もよくできてる。

底辺プロジェクトの DoNotCatchGeneralExceptionTypes

前回普通で当たり前な例外処理コーディングについて書いたとき、同様のやり方が FxCop の開発者のブログ記事でも推奨されている事に触れた。実は、その記事の本来の趣旨は、例外を基底クラスの Exception型 で catch しようとする無知な輩を啓蒙するというものだった。

今回は、その事について考えてみる。ちょっとクドクドとネチっこく考えてみる。

*以下、いちいち「例外を基底クラスの Exception型 で catch する」と書くのは面倒なので「基底キャッチ」と呼ぶ。

====
その記事で、件の開発者は以下のように告白している。

There are actually far too many catch (Exception) blocks in FxCop itself and it has caused us a great deal of grief. We’re working on removing them and I’m confident it will help us to deliver better software!

「実は、FxCop のソース自体に基底キャッチが濫用されていて酷い目にあったが、それを取り除いていくことで結局良いソフトウェアになった」と言う話。で、その教訓を踏まえて、基底キャッチがいかにダメな事なのか、基底キャッチ容認派への反論も交えて詳説している。

当の FxCop 開発者がそんな真似をしていたなんて意外だが、記事自体がもう4年半も前のものである事を考えると、相当に古い昔話なんだろうし、まあそんなものかと。でも、この記事やその他の同内容の言説のおかげで、基底キャッチが是か非かなんて問題を今さら蒸し返すような人は、少なくとも英語圏には少ないんじゃないかと思う。

====
ところが、未だに日本の底辺プロジェクトでは、基底キャッチで例外をもみ消して、「臭い物に蓋」よろしく無かった事にしてしまう手法が、当たり前のように日夜実践されている。しかも「教科書通りに行かないプロの現場で禁じ手を使いこなしてる俺って、やっぱプロだぜ!」てな感じで、一部の底辺プログラマが得意気な感じだったりもする。

そんなドン底の世界では、こんな事がもっともらしく力説される。

「うちのアプリは何があっても落とさないポリシーだから、どんな例外だろうとスルーは容認できない。防止も予測もできず、従って対処法の分からない未知の例外もあるだろうが、そう言うのはもみ消してでも続行させるしか手はない。それがうちの流儀なんだから仕方がない。」

で、全ての例外を漏らさないために、基底キャッチが導入されるというわけだが、まずそもそも、そんな想定外で正体不明な例外をもみ消したあと、アプリがまともに動くってどうして保証するんだろう? DBに不正データを作ったり、誰かの給料を間違って計算したり、外部のサービスに不正なリクエストを送ったりしないと言えるのかと。そんなの技術以前に、論理的に矛盾してるし、言える訳がない。

そもそも顧客は何て言ってるんだろう。 「Null参照でアプリが落ちると嫌だから、取引先の公開サービスに不正リクエストを送っても良いから、動作続行させてください」なんて、言った人がいるんだろうか。でなければ、実行を続ける事で不測の重大事態が起きないと限らない事を伏せたまま、うちのアプリは落ちませんとだけ、顧客に説明している事になるわけで、それこそ大問題のはず。

====
エンドユーザの使用中には自分のアプリは落ちてほしくない。落ちるのは嫌だ。落ちるべきでない。それに敢えて異議を唱える訳ではない。が、だからと言ってアプリの誤動作の方がマシと言う事にもならない。だとしたら、いかに未知の例外によりアプリが停止する状況を極少化していくか、それしかないはず。

もうほとんど結論が出ているが、その前にもう少しだけ底辺プログラマの声に耳を傾けてみると、たいていこんな事を言っている。「発生し得る全ての例外の型をキャッチしていたら、いくつ catch ブロックがあっても足りない。現実的でない。」

この主張の駄目な点を指摘すると、以下の2つになる。

第一に、キャッチすべき例外が何か分かっていない。前回書いたフツーの例外処理の作法を分かっていれば、「全ての例外を…」なんて発想にはならない。発生の条件が既知であり、対象法がわかっている例外だけがキャッチする意味がある訳で、それ以外はキャッチせずにスルーしろと言っている。

第二は、第一のダメな点の原因でもあるのだが、やはりこれも「アプリを停止させるくらいなら、もみ消してしまいたい」という強迫観念に憑かれているという点。「アプリは停止できない」 → 「どんな例外もスルーできない」 → 「例外は須くキャッチすべき」という思考パターンだが、これが間違っているのは上で説明した。

予見できて対処できるものをキャッチするという、これこそ正に現実的としか言えない話しか、最初から誰もしていない。

====
これだけ言っても、まだ次のような言い分で食い下がる人もいる。

「そんな風に例外をスルーしてばかりいたら、その度にアプリが停止して、顧客がいちいち再起動する状況が頻発するから大問題になる。」

そんなの、例外とか catch の作法以前に、単に納品できる品質に至っていないという馬鹿な話でしかないんだが、実はこの辺りに問題の本質がある。

つまり、スルーした場合のアプリ停止にしても、もみ消した場合の不正動作にしても、それらは結果として出てきた現象なのでって、まず先んじて発生した未知の例外状況が、そもそもの原因としてある。

で、この「想定漏れ」や「考慮漏れ」、「テストから漏れたコーディングミス」などを含む原因側に働きかける事こそが、抜本対応と言えるわけだが、正にここにおいて、「例外スルー」と「例外もみ消し」は決定的に正反対の効果を見せてくる。

潔く例外をスルーする事は、結果としてアプリを落とす事になっても、結局は未知の例外状況の発生を減らしていく事になる。理由は、開発中に発見しやすく、従って現行犯的に処置できるため、もっとも効率がいいから。

一方、例外をもみ消す事とは、潜在バグを温存し溜め込んでいく行為に他ならない。健全な開発作業に当然含まれるはずの、意図しなかったのコードや想定から漏れていた条件を発見し、意図通りに直したり想定内に置き直していく営みが台無しになる。せっかく開発中に潰せたかもしれない未知の挙動を握りつぶして、客先まで不安定要因を持ち込んでしまっているわけだから、救いようがない。


かなりの極論になるが、突き詰めると、こういう選択になる。

数年に一回、未知の例外により停止するものの、その他は誤動作もない安定したアプリと、停止だけはしないが、年中不安定な動作を続けながら、しばしば深刻な誤動作をしでかし、しかもその障害解析が極めて困難なアプリとで、どちらが良いか?

使う側にとっても、作る側にとっても、どちらにとっても答えは決まってると思うんだが。

====
まあ、普通のプログラマは、分かり切った事なのに、ずいぶんくどいなあと思うかもしれない。自分だって「例外のもみ消しは、結局は品質落とすよね。」の一言で済む話だと、正直思ってた。そうした普通の感覚が成立しないどん底プロジェクトにいつ巻き込まれるか分からないから不景気って恐ろしい。

※ 私見だが、最初から checked と unchecked が峻別される Java の業界では比較的少なく、どちらかというと.NET 業界に多く見られる気がする。