dead」は、アジャイル界隈に醸成されていたある種の信仰を、過激な論調で批判したことによって大変なセンセーションを巻き起こした。強い言葉に対しては、やはり感情的な反応が多くなる。Dead?」という歴史的な会談の内容を見るに、DHH氏の発言は、規律(信仰)に対する単純なカウンターなどではなく、そこにはプログラムデザインについての重要な論点が含まれているように思える。全5回の会談の内、今回は第二回までの内容を検討してみたい。結局のところデザインの意思決定を行うのは開発者自身であるまず全体的な印象として、DHH氏と、TDD肯定派のBeck氏、Fowler氏の意見が真正面から対立しているという感じはそれほどなく、お互いに別のところを見ていて話がすれ違っているような印象を受けた。Shore氏の以下のツイートは、この件に関するTDD肯定派のフラストレーションを代弁している。2014DHH氏の想定するようなTDDは、Beck氏やFowler氏の想定するTDDとは異なり、単にやり方が悪いからうまく行かないだけで、DHH氏の発言は「わら人形論法」に過ぎないというのが、TDD肯定派の反論である。DHH氏の立場であり、ここがすれ違いの中心になっている。Beck氏は、DHH氏のTDD批判は、車を運転していて見当違いな場所に行ってしまったからと言ってそれを車のせいにするようなものだと言う。TDDがプログラムデザインに悪影響を与えると言った場合、デザインの意思決定を行っているのはプログラマ本人であり、TDDがデザインを決定しているわけではないと。それに対してDHH氏は、TDDを実践する事によって、あるいは実践しようとすることによって、プログラムデザインにある種の傾向が現れるのは否定出来ないと反論する。Coplien氏も同じような話をしていた。ゆびてくhttps://ubiteku.oinker.me/2015/07/27/why-most-unit-testing-is-waste/TDDを実践すれば自然にプログラムデザインが改善するなどということはあり得ず、良いデザインを実現するためには結局のところ自分の頭で考えなければならないというのが、Coplien氏の場合はTDDを批判する根拠の一つになっていた。ところが、TDD提唱者のBeck氏や、肯定派のFowler氏も、この件に関しては全く同じように考えており、TDDというのは彼らにとっては思考を助ける道具に過ぎないと発言している(なので適材適所で使う場合もあれば使わない場合もある)。本当の対立は、TDDを肯定するか否定するかという部分にあるのではなく、それを使えば自動的に結果が好転していくような銀の弾丸として祭り上げている勢力とそれ以外の対立という事で、このブログで繰り返し取り上げてきた問題と全く同形の問題のように思える。(6)」でも紹介した、TDDが及ぼすプログラムデザインへの悪影響について検討を行っている。Railsのようなフレームワークを利用した開発で(厳密な)TDDを実践しようとすると、各層を単独でテスト出来るようにするために間接層(indirection)やモックオブジェクトを導入する必要が出てくる。それが結果としてコードを複雑にし、メンテナンスを難しくするというのがDHH氏の指摘だ。間接層が絶対悪というわけではなく、間接層にも依存モジュールを置き換えやすくするというメリットがあるわけだが、それをテストをやりやすくするという目的のためだけに導入するのは間違っているのではないかという指摘である。opposed)」というDHH氏の主張である。凝集性と結合性、あるいは凝集度と結合度は、プログラムデザインの良し悪しを計るための最も基本的な尺度だと言われる。凝集度はより高い方が望ましく、結合度はより低い方が望ましいとされる。モジュール間の依存関係はプログラムの構造的にはっきりと見えるので、結合性というコンセプトは比較的理解しやすいが、凝集性というのは具体的にどのようなものを指しているのだろうか?凝集性・結合性は、多くの場合、相関があるとされる。具体的には「凝集度が高いモジュールは他との結合度が低いことが多く、逆に凝集度が低ければ結合度が高くなる傾向がある(Wikipediaより)」と言われている。DHH氏は「凝集性と結合性は頻繁に対立する」と言う。DHH氏の主張を簡単に図解してみよう。操作するため、そのままだとデータベースにアクセスせずにアプリケーションロジックをテストする事は出来ない。機能にアクセスするようにする。枠組みに載せる事なくテスト出来るようになるというわけである。この改造によって、確かにレイヤー間の結合度は下がったと言える。いう日常的に馴染みのない言葉だと分かりづらいが、凝集性とは平たく言えば「まとまり度合い」のことを指す。今回問題にされているレイヤーアーキテクチャ一般について考えてみると、レイヤーアーキテクチャとは、アプリケーションの構造の中での役割分担を見出して、それらを疎結合となるように分割したものだと言える。分割されたそれぞれの層は単一の役割を引き受けており、その意味では全体としては凝集性が高くなる、つまりまとまりのある構造になっているはずである。Cohesion)」と呼ぶ。論理的凝集とは、プログラムの構造、あるいは技術的な特徴によってまとまりを作ることであり、論理的凝集の尺度で言えば、レイヤーアーキテクチャの凝集性は高いことになる。Cohesion)」のことを指しているのだと思われる。Exchangehttp://programmers.stackexchange.com/a/167129論理的凝集は機能的な特徴よりも技術的な特徴でグルーピングを行うという意味であまり望ましくないと言える。データアクセスを行うモジュールをグルーピングすれば、それは論理的凝集を実現したことになる。実際にモジュールの境界を決めるのは技術ドメインではなくビジネスドメインなので、論理的凝集はここで問題となる。論理的凝集を実現することによって、結果的に機能的凝集を失う事になる。機能的凝集はビジネスドメインの概念によって決まるので、先ほどのレイヤーアーキテクチャの例で言えば、レイヤーを分割する事によって、ビジネスドメイン上の一つのコンセプトが複数のレイヤーの間に引き裂かれて分散してしまうことになる。一つのビジネスオブジェクト上の変更が複数のレイヤーにまたがって影響を及ぼすというケースは、フレームワーク上で開発する多くの人が経験しているのではないだろうか。Coherence凝集性は、英語では「cohesion」と呼ばれている。ライティングにおいて「文と文との繋がり具合」を指す言葉である。これは単に文の論理的なつながり、つまり構造的なまとまり表すだけで、意味のまとまりを表すのには別に「coherence」という言葉がある。どちらも「まとまる」という意味の「cohere」から来ているようであるが、それぞれ「構造的なまとまり」と「意味的なまとまり」で指す意味が微妙に違う。いう言葉を当てた方が良いのではないだろうか。いうのは、上の話を鑑みても、この二つを言葉(コンセプト)としてはっきりと区別しておくことが重要であるように思われるからである。オフトピックだとして既にロックされているが、「歴史的な意義がある」ということで残されている。Beck氏自らが回答しているのがその理由の一つだと思われる。その回答が聴衆にとっては少なからず意外な内容だったことも、この質問の重要性に寄与している。Beck氏の回答はシンプルである。「私は機能するコードに対してお金を貰っているのであって、テストコードの対価を受け取っているわけではない。「必要な自信を得るための最小限度」は人によって異なるだろうし、チームで開発する場合は、チームを構成するメンバーのスキルや性格に依存することになる。完全にその人(チーム)次第になるということだ。これはアジャイルの思想からすれば自然なことに思えるが、Beck氏の回答に対する反応を見る限り、少なからぬ驚きをもって受け止められているようである。Beckがこんな考えを持っていたなんて青天の霹靂である。Beckも当然そうしていると考えているはずだ。」Beck氏の回答に対して正面から反対している人もいる。コードは書いた本人だけでなく、事情を知らない他人がメンテナンスする可能性があり(そしてその人はその時点でチームのメンバーではないかもしれない)、当事者だけの文脈でテストの範囲を決めるのは良くないという指摘だ。TDDはテストファーストが前提なのだから、一部分だけテストするということがそもそも成り立たないのではないか、等々。別の回答では、TDDのT、つまり「テスト」という表現が「テストはきちんとやらなければいけない」という、TDDの本来の目的とは異なる品質保証の文脈を呼び込み、その結果としてカバレッジ云々の話になってしまうのではないかと指摘している。TDD界隈では頻繁に耳にする指摘である。Development)というものが生み出された。(DHH)http://david.heinemeierhansson.com/2014/test-induced-design-damage.htmlソフトウェア開発の世界にTDDが紹介された当時、最も説得力があり、そして魅力的に響いた格言は「テスト容易性(Testability)を追求すれば、自然に良いプログラムをデザイン出来る」というものだった。あるモジュールをユニットテストするためには、そのモジュールをシステムの他の部分から切り離して、独立して動作させる必要がある。coupling)」を実現することになる。テスト容易性は必ずしも良いデザインに結びつくわけではないと、DHH氏は言う。テストをしやすくするために、過剰な「間接層(indirection)」が導入されることによって、コードの簡潔さが失われてしまうと。いうデザインパターンで、アプリケーションのコアとなるドメインモデルと外部のインタフェースを明確に分離するためのテクニックである。階層化アーキテクチャの一種と言っても良い。ドメインモデルを「ポート」と呼ばれるAPI的な層(「ポート」という語はネットワークのプロトコル・ポートや電子機器の接続を行うポートを意識している)で取り囲み、さらにその外側に「アダプタ」と呼ばれる、様々な外部システムとのやり取りをポートに適合するよう変換するためのレイヤーを配置する。いう4つのポートがあり、それぞれのポートに外部システムあるいはインタフェースごとのアダプタがある。導入するという例で、これは完全にやり過ぎであると、DHH氏は批判しているわけである。Commandパターンとして切り出す。切り離して高速にテストできるようにするためのテクニックである。architecture自体を非難しているわけではなく、多種多様な外部システムに対応しなければならないシステムで役に立つ可能性については認めている。問題にしているのは、ただ「ユニットテストをやりやすくするため」だけの目的でこの複雑な構造を導入することについてである。間接層を導入してモジュールの置換可能性を高めるのは、機能的な要求があれば合理的な選択となるが、ただユニットテストのためだけにこれを導入するのは、犠牲にされたプロダクトコードの簡潔さとその見返りの釣り合いが取れないのではないかという指摘だ。test)で対応するべきだ、というのもなかなか説得力がある。そのように考えると、システムの中でユニットテストの対象となる部分はかなり限定されることになりそうだ(ユニットテストの定義をどう捉えるかによるが)。ドメインモデルのテストについても、データベースから切り離してテストしようとするのは時代遅れであると切り捨てている。これは筆者も同意する部分で、モデルにおいてはデータベースとのやり取りがむしろ一番重要なところであり、昨今のORMではインメモリデータベースを利用して簡単かつ比較的高速に結合テストが実施出来るため、これをやらない理由はほとんど無いように思える。過去の資料を当たる限りそんなことはなさそうだ。Beck氏による「間接層とリファクタリング」というコラムがある。リファクタリング―プログラムの体質改善テクニックhttp://www.amazon.co.jp/dp/4894712288/「コンピュータサイエンスは、間接層(indirection)を設けることであらゆる問題が解決できるという信念に基づいた学問である。DeBruler」TDDに欠かせないプラクティスであるリファクタリングは、つまるところプログラムに間接的な部分を加えるということである。この間接層というものの重要性を強調しつつも、しかし、間接層はもろ刃の剣であることに注意しなければなりません。1つのものを2つに分割するということは、それだけ管理しなければならない部分が増えるということなのです。オブジェクトが、他のオブジェクトに委譲を行って、その先もさらに委譲を繰り返すような場合、プログラムが読みにくくなるのも事実です。間接層は、最小限に絞り込むべきなのです。Fowler氏の反応について検討してみたい。Dead?」という対談を展開して行くことになる。dead」が公開されて激しく燃え上がっている中、Fowler氏はまず「UnitTest」というタイトルの記事を自身のサイトに投稿している。UnitTesthttp://martinfowler.com/bliki/UnitTest.htmlこの記事の中でFowler氏は、DHH氏の記事の内容には直接触れずに、そもそもユニットテストの定義って何なのだろうという話を展開している。いう部分をはっきりさせておきたいということがあったのだろう。ユニットテストと言った場合、それが低レベルかつシステムの中の小さなパーツを対象としていること、基本的にはプログラマー本人によって書かれること、といった部分については多くの人の同意を得られそうであるが、それとは反対に、最も意見の食い違いが発生する部分が「コラボレーター」をどのように扱うかという問題だと言う。とあるモジュールをテストする場合、そのモジュールが単独で機能するケースはどちらかと言えば少なく、何らかの依存するモジュールが必要となる場合が多い。この依存モジュールを「コラボレーター」と呼ぶ。Classsic派だったので、コラボレーターを切り離さないのはユニットテストの定義から外れるのではないかと批判されたそうだが、テストのターゲットはあくまでユニットの振る舞いであり、その内部構造は問題でないと考えているようだ。correctly.ここで筆者はうーんと考え込んでしまった。Fowler氏自身が書いているように、何をユニットと見なすかは状況に依存するので、いわゆる振る舞いがユニットを形成してさえいれば、その内部構造がいかに複雑であっても、それをユニットと見なせる可能性があるということだ。これはシステムというものが基本的には入れ子の構造になっていることを考えると当然のことだとも言える。概念的には入れ子のどこを切ってもユニットになり得る。なると、もはやユニットという言葉に意味はないような気がしてくる。APIなのか、UIなのか)、コラボレーターのセットアップにどれぐらいの手間や時間がかかるのか、そしてそのコストはプロジェクトにとって許容範囲内なのか、というような指標の方がテストというものを分類するためには適切なように思える。Thomas氏が言うところのアジャイルの価値とも共通する部分が多く、TDDというのはアジャイル手法全体のメタファーとなるような、言わばミニアジャイルのような存在と言っても差し支えないように思える。間違っているかもしれないが、アジャイル以前から長くプログラムを生業としていた人ほど、テストファーストという考え方に抵抗感を抱くのではないだろうか?TDD、あるいはアジャイルという考え方は、外部からの要件(「機能」)が、結果として、その機能を実現するために必要な「構造」を生み出す、という考え方である。これは、この命題だけに注目する限りは当たり前の事実のようにも思える。プログラマは、経験を積み重ねる内に同じような構造のパターンに何度も遭遇することになる。やって頭の中に有用なコンセプトが蓄積されて行く。それらがイディオムとかデザインパターンなどと呼ばれたりする訳だが、TDDの問題意識は、それらのコンセプトの適用範囲は思ったほど広くなく、そして耐用年数は思ったよりも短く、濫用すれば過剰性能を作り込む罠に落ちるというものであった。人間はどうしても知識を貯えてしまうので、解法を先に考えてしまうというバイアスから逃げるのは至難の業である。これが、経験者ほどテストファーストに抵抗感を抱くのではないかという、推測の根拠である。要件が構造を生み出す、というロジックはとても反論しづらい。何のためにソフトウェアを作るのかということを考えれば、それが至極当然の流れのように思える。ミニアジャイルであるTDDがプログラマの間でドグマ化してしまうのも頷ける気がする。経験者のバイアスが存在しない初心者のプログラマにとって、TDDは自然な手法になり得るのだろうか?TDD以前では、プログラムデザインのフォーカスはやはりプロダクトコード側にあって、より良いモジュール、あるいはより良いオブジェクトのデザインは何かということについて、様々なデザイン原則やパターンなどを学習し、デザインの質を高める事によって、プロダクトの保守性や信頼性を担保するという方法が一般的であった。TDDの出現によって、プログラミングのフォーカスは、プロダクトコードからテストコードに移動する。テストをしやすいようにプロダクトコードをデザインすると、結果的に、モジュール間の疎結合を実現でき、自然によいデザインへと導かれるというわけだ。テストコードを検証すれば要件との齟齬をチェックでき、テスト自体を実行してプロダクトコードの正しさも確認出来るという、まさに一石二鳥・三鳥の方法であった。実際に筆者も以前、初心者はTDDのやり方から学べば、自然にプログラムデザインも学べるし、管理者視点からは要件との齟齬もチェックしやすく、なかなか良いアプローチではないかと考えていた時期もあった。実際には様々な障害があることが分かった。テスト駆動で小さなステップを踏むことの難しさである。Development)TDDでは、API視点からプログラムをデザインすると言っても、実際はインクリメンタルに開発するために、どういうテストを書けばプロダクトコードの変更を最小限に出来るかというアクロバティックなことを考えなければならない。slice単位で開発するアジャイルの難しさの中心的な課題といってもよく、とても初心者が太刀打ち出来る問題とは思えない。Coplien氏の指摘にも通じるが、自動テストが(現実的に)カバー出来る範囲というのは思ったより広くない、ということである。これはつい最近、たまチームで遭遇したばかりの出来事だが、テスト(外部)からは期待通り動作しているように見えるのだが、実はプラスアルファの余計なことをやっていて、関係ないデータを壮大に破壊していたという事例があった。原因は、ライブラリのAPIについての理解が足りなかったという、ごく初歩的な問題である。いった問題を考えると、テストプログラムで担保出来る信頼性など、特に初心者を対象とした場合、取るに足らないことが分かる。Coplien氏が言うところの「テストがコードのクオリティを上げる訳ではない、上げるのは開発者自身である」。アイロニカルな反論ではあるが、TDDが解決していた(とBeck氏が考える)問題の一覧が挙げられているので、以下にまとめておく。一度に解決しようとするとハマってしまいそうな大きな問題にどう立ち向かうか?簡単に解ける小さな問題を積み上げていく(小さなステップ)インタフェースと実装の分離どうやってAPIが実装依存にならないようにするか?問題共有どうやってプログラマ同士の問題共有を正確に行うか?不安どうやってプログラムが期待通りに動作している事を確認するか?このようにして、TDDが解決しようとしていた問題点を並べてみると、問題そのものの妥当性にはそれほど異論はないように思えるが、その解決策については必ずしもTDDに限る必要はないようにも思える。前回の記事に書いたように同じ価値を実現できるなら、必ずしも誰もが同じ方法をとる必要はない。アプローチでも、API視点でプロダクトコードを検証する事は出来る。エラーの抑止、リグレッションテスト、あるいはドキュメンテーションという観点から言えば、テストをいつ書くかは問題にならないはずである。Coplien氏が指摘しているように、ユニットテストにフォーカスし過ぎる事は、より重要で本質的な問題、つまりビジネス価値の軽視につながる可能性もある。これは、TDDの手法そのものではなく、その問題設定に対する疑義である。Coplien氏の論文。testsなど)に軸足を移すべきである、というのがこの論文の論旨。ユニットテストに価値が無いのかという部分の説明についてはなかなか説得力がある。論点を挙げてみると、TDDの実践者が言及するテストカバレッジ100%には意味が無いバグを取り除く最大の機会はテスト以外のところにあるユニットテストにビジネス価値はほとんどないTDDはプログラムデザインに害を及ぼすユニットテストの維持コストは馬鹿にならないテストがコードのクオリティを上げる訳ではない、上げるのは開発者自身である一つ一つの論点を追いかけてみよう。テストカバレッジ100%には意味が無いCoplien氏によれば、一般に言われるカバレッジ100%というのは、計算理論の見地から言えばまったくのナンセンスであり、本当の意味で100%を目指すのならば、到達可能なパスの全パターンと、その時にアクセス可能なデータの全パターン、それら全部の組み合わせを検証しなければならないとのこと。なければ、正式にそのプログラムが「正しい」とは言えないのだそうだ。TDDのメリットとしては、もちろんプログラムの信頼性向上も謳われる事が多いが、いわゆる品質保証としてのテストとは区別される事がほとんどで、TDDとしてのフォーカスはプログラムデザインに当てられる事が多いように思う。この批判はTDDに対する攻撃としてはそれほど有効ではないように思える。ユニットテストを品質保証の根拠として捉える向きには、計算理論的にコードの正しさを証明する事は非現実的であり、ほとんどのケースでは恣意的な基準によって判定されていることを明らかにした、ということになるだろうか。そのような勢力(ユニットテストを品質保証の根拠として捉える向き)が存在するんだろうか...?それとは別にして、プログラマの仕事をメトリクスで計ろうとする事の落とし穴については注意深く考えた方がよいだろう。ないようなテストプログラムを書いているのを目撃した事がある。バグを取り除く最大の機会はテスト以外のところにあるこれはウォーターフォール時代から言われていた事だが、プログラムの品質にもっとも重大な影響を及ぼすのは、要求・ドメインの分析結果を設計に落とし込むタイミングである。その前提の上で、ユニットテストという高コストな手法でバグを潰すのは、果たして費用対効果的にどうなんだろうかという話である。TDDも設計手法ではあるが、Coplien氏は他の論点でも述べているように、TDDはプログラムデザインに害を及ぼすと主張している。プログラムの信頼性を向上させる手法として、TDDに従来の設計手法以上の価値があるとは認めていない。「コードのクオリティ向上に寄与するのはテストではなく、結局のところそれは開発者自身である」というのが、彼の基本的な立場である。ユニットテストにビジネス価値はほとんどないおそらくこれが一番重要な論点だと思われる。Coplien氏によれば、ビジネス要件から導出されたテストだけがビジネス価値を持つ。ほとんどのユニットテストは、ビジネス価値とはあまり関係のない開発者の独断によって設計されている。ユニットテストにビジネス価値と直結する部分が全くないとは言い切れない。キーとなるアルゴリズムを実装している部分などだ。基本的にビジネス価値はより粒度の高いテストで表現すべきである、というのがCoplien氏の主張である。ユニットテストの価値を計る方法として、もしそのテストが失敗したら、どのようなビジネス価値を毀損することになるかを考える。分からない、あるいは明確でない場合、そのテストのビジネス価値はほとんどゼロだということになる。価値が無いのにも関わらず、そのテストを維持管理するのにはコストがかかる。あれば、そのようなテストは捨てた方が得策である。TDDはプログラムデザインに害を及ぼすこれはDHH氏も指摘していたが、TDDがプログラムデザインを改善するという客観的な根拠や事例はなく、それを調査した研究によればむしろ逆の結果が出ているようだ。Quality?http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.304.1723&rep=rep1&type=pdfTDDが及ぼす悪影響として、プログラムを過剰に細分化してしまうため、ドメインとプログラムで表現の粒度が釣り合わなくなり、プログラムが理解しづらくなる事、ユニットレベルでのビジネス価値が小さくなってしまうこと、などが挙げられている。仮にCoplien氏が言うように「テストがコードのクオリティを上げる訳ではない、上げるのは開発者自身である」のであれば、TDDがデザインに与える影響を客観的に計る事自体が無理筋であるように感じられるのだが、どうだろうか。ユニットテストの維持コストは馬鹿にならないこの問題はTDD支持者の中でも多くの人が認識するところだと思う。テストコードもプロダクトコードと同様(あるいはそれ以上に)、デザインとメンテナンスにコストがかかる。テストコードの設計においては、プロダクトコードとは異なる方法論を採用する必要があり、これが想像以上に難しい。テストコードがあるというだけで良しとしているプロジェクトも多いが、テストの品質がプロジェクトに与える影響は想像以上に大きい。プロダクトコードの設計を改善するためには、より良いテストの設計手法を学習しなければならないが、そのためには前提としてプロダクトコードの設計手法に精通する必要がある。プロダクトコードの設計手法に精通しているのなら、何故設計手法としてのTDDにわざわざ手を出さなければならないのかという矛盾がある。coupling(結合度)とcohesion(凝集度)を改善するために、テストを書くというのがTDDであるが、テストコードがプロダクトコードに強く依存してしまう(高結合度)という問題がある。プロダクトコードに大きな修正が入った場合の、テストコードに及ぼす影響は甚大であり、粒度が小さく数の多いユニットテストのメンテナンスコストは想像以上に大きくなる。テストがコードのクオリティを上げる訳ではない、上げるのは開発者自身であるユニットテスト・TDD批判のベースにある考え方であり、これはある種の正論として多くの人が賛成出来る部分では無いだろうか。Dead」宣言にも見られるように、いわゆる銀の弾丸的な存在としてアジャイルやTDDがあるように喧伝されるために、このような違和感を表明しておきたいというのは多分にあるのではないかと思う。Coplien氏は、件の記事の締めくくりに、コンピュータ資源が豊富になって何でも簡単に試したりやり直したり出来るようになると、自分の頭で考えなくなってしまう(ツールのアウトプットを盲信してしまう)とう話をしている。Blog.投稿先:キャンセル。