むでぽんスマートフォン用の表示で見るやっとむでぽん2016-03-28C言語でTDDを学ぶ?TDDでC言語を学ぶ?Facebook上で議論になったのですが、C言語でTDDを勉強していると、「よりよい設計」になりにくいという話になりました。例として整数の区間の最初のほうの課題を考えてみます。}問題:どうしたら関数を導入できるでしょうか?元のお題はオブジェクト指向を想定しているところがあって、「生成しよう」でオブジェクトを作り、「取得しよう」でオブジェクトから値を取り出すという形になるんですが、C言語では「値を与えて取り出す」のならば、変数があればいいよねと。グローバルでも構わない、というのは、お題(要求仕様)に何も書いてないから。(externがないのでモジュール内に閉じていますけれど。逆に、プロダクションコードとテストコードを別モジュールにしたら、グローバルになってしまいますね。)TDDのやり方に律儀に従うなら、新たなテストによって現在の(グローバル変数だけで生成は関数不要という)設計の限界が明らかになり、よりよい、シンプルな設計が導かれます。区間のお題の続きに「閉区間が別の閉区間と等しいか」があるので、複数の区間を扱うにはグローバル変数では困るよね......と思いきや、こうなります。}うん、グローバルに設定したものとパラメータで渡すものを比較すればいいよね......間違ってはいない。TDDでシンプルな解を追求するTDDには(ひいてはXPには)「シンプルさ」という価値があります。TDDではシンプルな解をよしとします。ここには「(与えられた問題や制約をすべて充足するなかでもっとも)シンプル」という条件が隠れています。テストコードを眺めて、「比較対象を片方は変数に、片方はパラメータで渡すのは、シンプルでない」と思えば、以下のように書き直せます。}しぶといな、グローバル変数。TDDが自動的に良い設計を保証するわけではありません。「TDDを使ってよりよい設計に到達する」のと「TDDを使えばよりよい設計になる」の間には、深い深い溝があります。TDDを使うと設計品質が悪くなるという調査もあります。少なくとも、何が「良い設計」であるか、言語やライブラリやフレームワークのどんな特性を利用すれば「良い設計」になるか、知識がなければ設計として実現できません。元々のお題にはありませんが、このようなグローバル変数を使った設計に対しては、別の課題を設定してあげるという手を思いつきました。グローバル変数を避けるべき理由として「いつの間にか値が書き換わって危険」という知識があります。range_equal()を呼ぶと書き換わってしまうので、自明でない挙動をする。そこで「比較しても値が書き換わらないようにする」という課題が考えられます。それをテストで表現すれば、TDDの流れで設計を改善できます。}このテストは失敗するので、グリーンになるように実装を直します。目指すのはグローバル変数の除去ですが、そうすると実装を直す過程で上端・下端を生成したり取得したりというテストが、成立しなくなってしまいます。おまけに今書いたばかりのテストも、中身がなくなってしまいます(EXPECT_EQで比較するものがなくなってしまう)。}なくなってしまったテストは、消してしまうのがいいでしょう。万が一あとで復活させたくなっても、一度書いたことはきっと憶えているはずです(それにバージョン管理してますよね?)よい設計を目指すTDDさて、話を前に戻して、「比較しても変化しないこと」という課題は、グローバル変数の除去を意図して導入しました。グローバル変数を除去したかったのか?不用意にグローバル変数を用いるのは「悪い」設計で、しかしTDDのサイクルの中でグローバル変数を止める自明な方法が見つからなかったためです。ここではいくつかの議論が考えられます。Facebook上の組み込みTDD勉強会(クローズドグループ)での議論を踏まえています。みなさんありがとうございます。グローバル変数の利用のような初歩的な設計上の問題は、設計判断を持ち出すまでもなく常識的に取り除く(最初から使わない)べきだ。したレベルならコーディング標準で制限できるし、そういうレベルのプログラマはしっかり教育したりレビューで改善すべきだ。このタイミングで無理にグローバル変数を除去する根拠はない。お題を進めて仕様を拡充していけば、自然にグローバル変数を止めたくなるときが来るはず。そのときにこそ、最適な設計判断ができるのであって、いま強引に変えるのは拙速で、YAGNIでムダだ。C言語ではデータ構造の自由度が(純粋なオブジェクト指向プログラミングに比べると)高く、初期に十分検討しないと後でリファクタリングするのに苦労する。TDDでもC言語を使うときは事前設計をもっと念入りにやったほうがいい。「シンプルにする」というTDDの方針だけでは、必ずしもよい設計に到達できない。十分な知識と広い視点で、より望ましい設計を考え、そちらに向かって(やや強引であっても)TDDで進んでいけばいい。いずれの言い分も妥当ですし、環境や状況や人によって重視すべき事項は変わってきます。私自身は最後の考え方がわりと好きで、よりよい設計を仮説として(できればテストに書き)、TDDで実証するというアプローチを選ぶことが多いです。グローバル変数の話は、これでいったん片付いたことにして、データ構造を整理することを考えてみます。上端と下端は常にペアで使うものなので、ひとまとめにしたいですね。方法はいくつか考えつきます。構造体を作る長さ2の配列を作る1個のINT32の上位16bitと下位16bitに格納するなどどれが望ましいでしょうか。C言語の適用範囲を考えると。いずれも妥当な解法になり得ますが、ここでも「シンプルに」、つまり「与えられた仕様を満たす範囲でできるだけシンプルに」考えたいです。メモリや処理時間の制約がなければ、構造体が素直でしょうか。}構造体を導入したコードを見ると、どうもテストコードが煩雑で、これなら前のほうが良かったように見えますね。rangeを簡潔に生成する方法を導入すれば改善できそうですが、本稿では省略します。知らない解法を使えるかまた話を少し戻して、構造体導入の判断を考えてみます。構造体を使おうと思うには、少なくとも構造体を知らないといけない。カジュアルにグローバル変数を使っちゃうプログラマーが構造体を知っているか、知っていても適切に使えるかは疑問です。知らない解法を使うことは誰にもできません。ここでは構造体を取り上げていますが、「知ってはいるけどちゃんと使ったことのない○○○(任意の手法・技法)」と考えれば誰でも遭遇し得る状況です。した状況で、TDDは無力なのでしょうか。勉強した熟達プログラマしかTDDは活用できないのでしょうか。その逆で、TDDは新しい技術を使いこなせるようになったり、未知の技術を発見する役に立ちます。それ自体がシンプルな解の探究になるためです。いまある問題に対してよりシンプルな解に到達するには、以下の4レベルを上がっていく必要があります。動くよりシンプルに書き直す他の書き方を複数試して、一番シンプルなものを選ぶさらにシンプルな書き方を勉強してくるグローバル変数の例ではレベル2を取り上げました。構造体を知っているけれどうまく使えないのであれば、レベル3にチャレンジする必要があります。先のコードでは構造体を導入したものの、かえって煩雑になってしまいました。よい設計、よりシンプルな解法を見つけるには、レベル3の試行錯誤が必要になってきます。試してみる、他のアプローチで試してみる、リファクタリングでいろいろなパターンを見比べてみる。その中で一番よいと思えるものを残す。TDDの回転は一直線ではありません。行きつ戻りつしながら少しずつ上がっていくのです。試行錯誤で最善と思える結果が出ても、まだよくなる可能性があるかもしれない。思えたときが、レベル4になってまったく新しい解法を探しに行くタイミングです。レベル3までをきちんとこなしていれば、問題について十分に理解できているはずです。いまある解法の効用と限界も知っているでしょう。それならば、新しい解法を見つけても適切に評価できます。中途半端な解法に飛びついたり、不適切な解法を盲目的にコピーしてしまうこともありません。レベル3までは問題を深く理解するためのステップで、レベル4が本当の問題解決だと見ることもできるかもしれませんね。その2引き続き、Copeが教えてくれた論文の紹介です。同じ人が書いた...2013-07-18TDDでデバッグするバグを見つけたとき、どうするか。プログラマなら3つの選択肢が...2012-12-23テストをリファクタリングするとき気をつけることTDDのサイクルはRed-Green-Refactoringです。福岡」をやってきました月別アーカイブはてなブログをはじめよう!yachさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。