BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ インタビュー Coplien氏とMartin氏、TDDとCDDそしてプロフェッショナルの定義について大いに語る。

Coplien氏とMartin氏、TDDとCDDそしてプロフェッショナルの定義について大いに語る。

ブックマーク
   

1. Floyd Marinescuによる導入部分

Floyd Marinescuです。私は今、JAOOカンファレンスでJim Coplien氏とBob Martin氏と居ます。我々には、TDDの価値が何なのかというところで、とても興味深い見解の相違があります。
そこで、それぞれが2~3分づつ発言しながら、議論をしましょう。
私はあなた達の話を聞いていますから。

   

2. Bob Martin: まず最初にこれを言っておく必要があります。今私は、自分にとってヒーローと言える人の隣に座っています。私は1991年から1992年ぐらいにJim氏の本を読み、ソフトウェアに対する私の考え方を改めました。特にC++に対する考え方を。ですから、今この場にいられてとても光栄です。我々の見解の相違は、確信というわけでなくあくまで可能性ですが、観点の違いだと思っています。私の論点というのは、ここ6年の間に何が起きたかを考えると、テスト駆動開発していないソフトウェア開発者は"プロフェッショナル"であると自称できなくなってきているということです。

Jim Coplien: そうですね。昨日のあなたの基調講演では"テスト駆動開発"を使って何をしようとしているかという話でしたから、それはよいことだと思います。
私は、とりわけXPコミュニティがテスト駆動開発と呼んでいるものについてについて強く反対してきました。
過去6ヶ月の4っのカンファレンスで行われた多くのチュートリアルと聴講してみたのですが、彼らがテスト駆動開発と呼ぶものには、きわめて一貫した恣意的なストーリーが用いられていることがわかります。
しかし昨日の話しは、少し違っていました。そこで、この議論を有意義で意味あるものにするために、まずあなたの昨日の話を簡潔に繰り返してくれますか。そうすれば、前提を合わせて話ができます。

   

3. BobはJimの要望に応え、三つの原則を紹介している。

最初の一つは、テスト駆動開発者は、失敗するユニットテストを書くまでは、プロダクションコードは1行たりとも書かないというこです。つまり失敗するユニットテストが存在していないのであれば、プロダクションコードは書かれていない筈です。
二つ目の原則は、テストを失敗させるために充分なユニットテストがあれば、それ以上ユニットテスト書いてはいけないということです。コンパイルできないという状態は失敗に含みます。
ですから、プロダクションコードを書いてないうちからユニットテストをたくさん書くことはできません。
三つ目の原則は、テストをパスさせることができる充分なプロダクションコードがあれば、それ以上プロダクションコードを書いてはいけないということです。
ユニットテストは書きかけで、それを脇に置いてプロダクションコードを書くということはやってはいけません。
この三つの原則は、およそ30秒ぐらいのサイクルで行います。テストを30秒から1分ぐらいで行うことで、ユニットテストとプロダクションコードを実質的に同時に書くことが重要なのです。
これが私の定義です。

J: いまのところ、私がTDDについて抱いている主な懸念は、あなたの今の発言に関する限り、あまり問題ではないと考えています。ですから、もし、それ以上でもそれ以下でもないのならば、私たちに考えにズレははないと思います。
では、私の懸念は何なのか、というと、自分が多くの顧客と幅広い仕事をした中で体験したり、他のコンサルタントやスクラムマスター達が彼ら自身のプロジェクトで経験した話を聞いているうちに気づいたものなのです。
私たちは今まで2つの重要な問題を確認してきました。1つ目の問題は、TDDでは実際に稼働している環境と同じアーキテクチャやフレームワークが無い状態でテストされているという点です。これはKent氏が最初の頃に強く主張していた「TDDをアーキテクチャを駆動するために使うのだ」というもので、ユニットテストすることで、手続き型ボトムアップアーキテクチャの原因になります。
私たちは今さっき上の階で「TDDはユニットテストと同じなのか?」という議論をしていましたね。
まあ、同じではないと思います。Fortranではユニットテストというのは素晴らしい考え方でした。APIを階層化したときにソフトウェアの構成要素としてのユニットは、ユニットテストと一致させることができたからです。しかし、今やソフトウェアの構成要素としてのユニットはオブジェクトなのに、テストしているのは手続きです。そこにはちょっとした不整合があるのです。
つまり、アーキテクチャを駆動するためにこういった手段を取るならば、2次元データから3次元データを作り出すようなもので、結局失敗すると思います。
アーキテクチャ上の問題で自分自身の首を絞めてしまうことで、3回目ぐらいのスプリントには、もうこれ以上何もできない状態になってしまい、挙げ句、衝突、爆発炎上して失敗する。こういった光景は多くのプロジェクトでよく見られるものです。
そういう場合はリファクタリングをすることでこの状態から抜けることはできなくなっています。クラスのカテゴリ、階層を超えてリファクタリングしないといけない状況に陥っているので、リファクタリングしても同じ機能を維持しているかの保証ができないのです。
もう一つの問題は、Trygve Reenskaug氏と私がGUIの破壊にみられる現象についていろいろと話していた件です。Javaクラスラッパのような手続き的アーキテクチャを例に出すと、そこにはオブジェクト指向でそうしているような、ドメイン知識やユーザのコンセプトモデルに沿った形での構造の決定がもはやできなくなっているのです。
Kent氏でさえ、しばしば「良いGUIを以てしても拙いアーキテクチャは隠しきれない」と言っています。
アーキテクチャは常にインターフェスを透かして見えてくるものだと思います。さらに言うと、ドメインモデルがインターフェースにどう影響するのか、という構図はインフラストラクチャにあるのですから、インフラストラクチャは重要なのです。
ですから、ボブおじさんの三つの原則を当てはめようと思うなら、そうすること自体は問題ないでしょうが、私は、構造的な側面を把握できるような出発点があるとよいと思います。

   

6. 確かにその通りです。ちょっと話を元に戻して、他の視点で考えてみましょう。アーキテクチャというのはとても重要だと考えています。私自身多くのアーキテクチャに関する論文や書籍を書きましたし、アーキテクチャのかなりマニアだと思います。一方で、アーキテクチャというのは一枚布のようなもので出来るとは思っていません。ちゃんとした設計とアーキテクチャのスキルを使いながら、週や月単位のイテレーションを何回も繰り返し、アーキテクチャを少しずつ積み上げるやり方をするのが普通だと思います。そして、作り出したアーキテクチャを構成する要素のうちいくつかは、捨ててしまうこともあるでしょう。そういう時は、イテレーションを数回使って別のアーキテクチャを試したりすると思います。2~3回のイテレーションを経て、正しいと思えるアーキテクチャに落ち着いた上で、調整フェーズに入ります。つまり、私の考えは、コードを実行することで情報を得て進化し、テストを書くことでも情報を得て進化する、そういうものがアーキテクチャなのです。

J:アーキテクチャが進化するというところには同意します。アーキテクチャというのは、書き上げたコードと、おそらく早い段階で提供されているであろうユースケースとの両方を使って形成していくものだと思います。アーキテクチャはスコープであったり、他のそういった類の情報も与えてくれますが、インクリメンタルにアーキテクチャの設計をやろうとしたならば、文字通り全てがインクリメンタルになってしまいます。ドメイン知識が事前に得られていない状態で顧客と作業を進めてしまうと、完全に間違った方向に進んでしまう怖れがあります。
かつてKent氏と話をしていた時のことを話しましょう。彼がTDDや、動作しうる最も単純な方法で実装しようという意味でYAGNIという言葉を提唱したばかりのころ、彼は「よし。銀行の預金口座を実装しよう。」と言いました。

預金口座っていったい何のことでしょうか?
足したり引いたりできるだけの数字のことです。つまり預金口座というのは計算機のことですね。
では、差引残高に足したり、そこから引いたりするのを表示する計算機を作りましょう。
これが動作しうる最も単純な例で、他の全てはその発展形であると言えます。
もし、本物の金融システムにしようとしても、預金口座はオブジェクトでさえないので、そこからリファクタすることで正しいアーキテクチャへ導けるようなものではありません。
預金口座というのは、監査証跡をしつつ、預金、金利の集計、その他のお金の動きなどのデータベーストランザクションを繰り返し処理する一連のプロセスのことです。
ユーザの立場から見た場合、預金口座は銀行のどこかの棚にいくらかのお金が置いてあるのとは訳が違います。ご存じのように、金融システムの基盤では、税理士や保険計理士、そういった民間の人達をサポートするために、他と比べて複雑な構造になっています。だから、インクリメンタルな手法で取りかかれないのです。
いや、不可能とまでは言えないですね。銀行業界が40年かけてここまできたように40年ぐらいかければ。
そこに40年を費やすことができますか?でも、それはアジャイルとは言えませんね。

ですから、前提知識を活用するべきなのです。確実な決断だけでも最初にいつくか行っておくべきでしょう。そうすることで、決めきれなかった事項を後になって検討する時に、その判断を容易にしてくれるはずです。
確かに状況は変わりますし、アーキテクチャは進化します。「アーキテクチャを塩漬けにしなさい」とまで言う人はいないんじゃないでしょうか。
また、前もって適切なコード、つまり実際のメンバ関数として書かれるコードということですが、それができるとは思えません。
見た目を書く、ロールを書く、ドメイン知識の構造を表現できるようなインターフェースを書く...
そうして書いたものの中身のコードを書くのは、お金を払ってくれるクライアントを見つけてからです。そうしないとリーン原則に違反してしまうからです。
「ジャストインタイム」方式でやるならば、最初から構造を押さえておこうとするべきです。そうしないと、リスク管理しているつもりで、自分自身の首を絞めているようなものです。

   

7. では、小さな誤解についての説明と、いくつかそれに対する反論をさせてください。私は、抽象的なメンバ関数や存在しないメンバ関数を持つようなインタフェースを実装することは、まずありません。適切なインタフェースを持つオブジェクトを作るようにしています。Javaで説明すると、そこに何もないような「なんとかインタフェース」を作ることはあったかもしれないですが、いつか実装することがあるかもしれないと、たくさんのメソッドを持つインターフェースを取り入れるようなことはしていません。それが、テスト駆動や要求駆動を使おうとしている理由です。インターフェースを分離したほうがいいきっかけとなるアーキテクチャの不和を、鵜の目鷹の目で探しています。

問題なのは、いわば、言葉というものが定義とは別に意味を持っているということです。
何かをラバと呼んだり、ラバとは何ですか?と問うことをしなくても、ラバはラバという事実は変わりません。
リンカーンが言ったように「ラバをロバと呼んでみても、ロバになるわけではない。」ということです。
つまり、メンバ関数がセマンティクスとして、意味を教えてくれています。
やりすぎたり、当てずっぽうにやってはいけない...この点ではKent氏に同意できます。「XP入門」で彼が言っているように、「当てずっぽうにやってはいけない」これは真実です。

しかし、ここで私の知っていることを確認しておきたいと思います。いくつかご存じのこともあるかとは思いますが、通信システムや金融システムの仕組みについてです。
"recovery"オブジェクトを作ってはいけないということはご存じかと思います。
私はかつて、大きな通信会社のプロジェクトの再構築に携わっていました。そこではオブジェクト指向と最新のコンピュータサイエンスの技術を駆使して、使用料の切換システムを作り直していました。しかし、そのプロジェクトで私に任されたのは"recovery"オブジェクトを作るような輩と一緒に仕事することでした。
とてもばかげた話です。"recovery"はオブジェクトではないのに。けれども彼の薄っぺらいドメイン知識ではそうするしかなかったのでしょう。
そのオブジェクトのメンバ関数の正体を突き止めてみれば、それがオブジェクトですらないことが理解できると思います。
そこで、こう聞きます。「どうして私がオブジェクトになってないと気付いたと思う?どんなメンバ関数があると思う?」
「う...ええと、recover関数です。」
「素晴らしい。そいつは助かるな。」
現に、私はそこに今はSOAと呼ばれている技術を活用している人達がいることに思い当たりました。
これは危険だな、と。
オブジェクトに意味を与えるには、何か実体が必要なのではないでしょうか。

   

10. では、話を元に戻しますが、実行可能なコードを書き始める前に、どれぐらいの時間を使いますか?例えば、最終的に200万行ぐらいになるシステムだとしたらどうでしょうか?

うーん。200万行という規模は、経験上かなり小規模ですね。
私は何億行という規模のシステムを開発していますが、最初にコードを実行する前だとすると... 個々のシステムによると思いますが、シンプルな通信業界向けのシステムであると仮定しましょう。
きっとこうすると思います。C++のシステムという前提です。少なくともコンストラクタとデストラクタは、然るべきものを作ります。あとはオブジェクト間の重要な関連付けができるように...

   

11. テストは書かないのですか?関連をテストするための。

書くと思います。
間違いなく書くテストは、例えばシステムの起動や終了時にメモリが正常であるかなどをきちんと確認できるテストです。30分といったところでしょう。

   

12. 素晴らしい!では我々の相違点はどこにあるのでしょうか?おそらくそれはTDDとプロフェッショナリズムの考え方についてではないでしょうか。次はそのことについて話しましょう。

そのことについては個別に反論はありますが、我々の間では解決できるものだと考えています。それはそれでよいのですが。
これを聞いている人達のために再度明確にしておきたいと思います。正しいやり方をしている人に出会えた時に、以前からこの種の問題の回避方法は、話合われてきたのだと考えるようになりました。そうしたことは、TDDの書籍や、TDD入門では扱われていません。
うまくやっている人たちも、例えばDan North氏が今BDDと呼んでいるような手法に乗り換えてきています。私はBDDは本当に素晴らしいと思っています。(RSpecや、低レベルな部分に引きずられているところを除けば、ですが。)
すでに正しいやり方をしている人はたくさん居るのに、それをTDDと呼んでしまっています。その結果、本を買ってTDDを調べ「アーキテクチャはテストによってしか得られないのだ」という古い考え方に辿り着いてしまうことを懸念しています。そういうセリフをこの6カ月のチュートリアルで4回聞きました。あなたが言ったようにそれはただの馬糞です。

ところで、今はプロフェッショナルの議論ということですが、あなたがプロフェッショナルについて考えがあるのであれば、それはどのようなものでしょうか?

   

13. TDDを実践している人達、でしょうか。

私にとって「プロフェッショナル」というのは単にその分野で仕事をしてお金を稼げる人のことです。

   

14. ええ。それは私も理解しています。ただ、私はそこから一歩進めたいのです、なぜなら我々の業界はどこかプロフェッショナルの基準というものに欠けているからです。

スタート地点として、あなたの定義を確認する必要がありそうですね。そうすれば議論ができると思います。

   

15. 実際に定義があるわけではなくて、口上みたいなものです。私が思うに、近頃ではユニットテストをしていないコードを納品する開発者は無責任であるということです。これを正すのに一番良い方法は、TDDを実践することでテストしていないコードが納品されないようにすることです。

私は同意できません。
ここは重要で深いポイントだと思うので、例を出して反論させてください。
他の例も出せますが一例として。コードインスペクションやペアプログラミングはとてもよいもので、きっとすごい価値があるという見解に対して、手を振り回してなんやかんやいちゃもんを付けることもできますが、それは別の機会にしましょう。
より重要で、的を射ていると思っていることを言わせてください。
まず、ユニットテストが何なのかということに注目してみましょう。
ユニットテストが何なのかというと、APIや、引数を持つ状態空間がうまく動くか調査するもので、それが半ダースとか百とか、場合によっては数百万(2の32乗)ぐらいの数になることもあります。それを淡々と試行してゆくわけです。
これは本当に発見的なやり方です。そうやってバグを見つけられるのは本当に幸運としか言いいようがないです。
私がより効果的だと考えているのは「契約による設計(Design by Contract)」です。
「契約による設計」では事前条件と事後条件と、そして不変表明を書きます。
ただ、殆どの言語ではこの技術はサポートされていません。
この技術は静的に条件のチェックができる言語でしか使用できないし、Eiffel以外の言語では成熟しているとは言えません。しかし、似たような機能を基盤技術に追加することはできます。
「契約による設計」はあらゆる面でTDDより優れていると考えています。例えば、コードを堅牢にしたいときや、外部からの見え方に焦点を当てたいとき、などが長所です(そう私は思います)。
そして、少なくとも自分にとっては、この「契約」はテストよりも効果的だと気付きました。
さらに言うと、より広範囲なカバレッジを実質的に保証してくれます。ただ無作為に適当な値をいくつかばらまくより、引数の入力範囲をカバーすることができるからです。
最近、Bertrand Meyer氏はこの概念をさらに進め、CDD - 契約駆動開発(Contract Driven Development)と呼んでいるものに取りかかっています。これは何をしているかというと、まず「契約」を設定し、それに基づいた任意の値をいくつか入力してみます。ここで事前条件を満たしていなければ、それはテストは失敗することがわかるので、実行しません。テストをしてみて実行後に事後条件が満たされていれば問題無いですが、そうでなければバグです。
彼らは実際にこの手法を行ってみました。
テストを自動実行できるツールも使いました。
Eiffelライブラリで、1週間使ってこれを実行してみたところ、20年間使われてきたEffielライブラリで、7つのバグを見つけてしまいました。これはちょっと興味深いですね。
ところで、契約による設計では、ビジネス的な意義を突き止めうる方法で、コードを意識的に表現していますが、TDDで問題だと思うのは、多くの開発者がビジネス的な意義をクラスレベルまで落とさないといけないということです。これは事実そうなっていると思います。時としてビジネス的な意義を突き止めるための手段が、クラスレベルでAPIを追跡するしかないことがありますが、それは本当に大変なことです。

   

16. そういうトラブルは私も経験があります。この話はずいぶん昔に一段落したと思っていました。Eiffelと「契約による設計」を思い出していますが、全てのメソッドに事前条件、事後条件、それからクラスの不変表明を明確にするんでしたよね。テスト駆動開発、またはユニットテストの組み合わせでもいいですが、実質的には同じことをしています。メソッドの引数の入力や戻り値のチェックの組み合わせを明確にしたり、あなたが言っていたような状態空間の調査もします。いつも思うのですが、それは表裏一体の関係です。いつでも契約をユニットテストに置き換えることもできますし、逆も可能です。ただ、あなたも知っているように私は依存関係マニアなので、依存関係の方向が違ういう点が異なるとは言っておきましょう。ユニットテストはプロダクションコードに依存しています。これは問題無いと思います。そしてプロダクションコードはユニットに依存していません。一方で契約はプロダクションコードを汚してしまい、それが悩みの種になります。

それは無理に二元的な考え方をしているのではないでしょうか。考えるべきは一つしかなくて、それはコードであり、コードはつまり設計そのものです。それが成果物です。それ以外はリーンとは言えません。
ユニットテストを使っている典型的なプロジェクトでは、コード量とテストの量はだいたい同じです。そしてコードがあるところにはバグがあります。
自分の作業速度を半分にしているようなものです。
よく知られている例として一番有名なのは、ADAコンパイラがテスト駆動開発を使うことで、逆にバグを増やしてしまったというものです。これは、テストの量を増やすことで、コードの量を増やしてしまったことが原因です。
(「契約による設計」の)表明を使えば、インタフェースが持つセマンティクスとコード自体との間に良い、というかむしろ不可欠の結合を持つことができます。
一方で、テストを使った結合は、はるかに厄介なもので、管理しにくいと言えます。
もう一つ反論しようと思っていたポイントがあります...

   

17. コードの量に対する考え方の違いに驚いています。

実際にどうやって使われているかを見てきましたが、(「契約による設計」の)表明のように書かれたJUnitテストは良いものだと思います。しかしそういうふうに書かれていないテストが多かったです。

   

18. 厄介なテストがあるというところは同意しますが、厄介なコードもあります。あなたは「ツールは誤用されがちなので、それを使うべきではありません。」ということを理由にしていますが、それは良くない考え方です。それを言ってしまうと、何もできなくなってしまいます。

いえ、そんなことは言っていません。
私が言っているのは、幅広い手法のなかで実際に使われている手法を、私がどう見ているか、と、それから、それがちゃんと理解されていないということです。

   

19. なるほど。そうですか。では、「契約」は広く誤用されていますか?

そもそも、広く使われているとは言えませんね...

2008年5月14日

BT