読者です 読者をやめる 読者になる 読者になる

TDDでのスクラッチコーディング

オブジェクト指向言語でのTDDの問題

 テスト駆動開発ではコードが部分最適を取り全体最適にしずらい、なんて指摘は、例えばここのように時々言及されています。そうした背景からテスト駆動開発では、スマートな実装を進めるために、ある程度アウトサイドイン開発の視点が必要だと一部で言われています。

 アウトサイドイン開発とは、簡単に説明すると、Mockやスタブ等を使って、上位レイヤ(呼び出し元の関数・クラス)から、下位レイヤ(呼び出し先の関数・クラス)へ、開発を進めていく方法を指します。そうした方法を取ると、「どういう使われ方をするか」「どのような機能を要求されているか」というイメージを明快にした状態で下位のクラスやメソッドを実装できるため、全体的な視点で見て冗長な機能・設計の実装を削減することができます。決定的な改善策ではありませんが、部分最適の問題の改善を促進できる方法の1つと言ってよいでしょう。


 ただこのアウトサイドイン開発は、C言語のような非オブジェクト指向言語では実施が困難であることが少なくありません。理由はいくつかありますが、大きい要因として、上位の関数を実装する際に下位レイヤの代替となるスタブを実装しにくい、というものが挙げられます。

 特にテストごとに複数種類のスタブを自動で切り分けるコードが大抵の場合面倒になります。例えばCでそれを実行しようとすると、外部スコープ変数やプリプロセッサをうまく使ってそれなりのテスト環境を構築する必要が出てくるでしょう。ポリモフィズムを使って簡単に実現してしまうオブジェクト指向言語と比べると、手間はかなり大きいといえます。

スクラッチコーディングによるアウトサイドイン開発

 そこで今回のテーマですが、この非オブジェクト指向言語ではアウトサイドイン開発を取りにくい、という問題への対策への1つとして、個人的にスクラッチコーディングというものをやっています。スクラッチコーディングというのは、その名の通り「実装イメージを具体化するために、使い捨てのコードを書く」というものです。


 例を出します。

 例えば「ip_ctrl()」「load_buffer_data()」のような関数を実装したい際は、その実装イメージをスクラッチとして以下のように書き出してしまいます。以降は、スクラッチであぶりだされたget_buffer_size()等の関数や、それより下位の関数をTDDで実装していくことになります。

#if 0

/*IPコントロール*/
void ip_ctrl(void)
{
    load_buffer_data();
    
    open_ip_pins();

    ip1_ctrl();
    ip2_ctrl();

    close_ip_pins();
}

/* リングバッファの格納データをデコードしIP部に通知する */
void load_buffer_data(void)
{
    buffer_size = get_buffer_size();

    for (i = 0; i < buffer_size; i++) {
        sbuffer_data buffer_data  = get_buffer_data();

        if (check_buffer_data(buffer_data)) {
            log_error();
        } else {
            ip_data_setting(buffer_data);
        }
    }
}
#endif

 ここでの記述は、あくまで上位レイヤの関数の目安であり、コンパイル可能・実行可能にこだわる必要はありません。例えばopen_ip_pins()やcheck_buffer_data()といった下位の関数のスタブは特に作成しません。文法の整合性も、IDEのフォーマット整形やインデント調整機能等に支障が出ない程度で十分です。大抵の場合、「#if 0 〜 #endif」のコメントアウト構文は最後まで取り払われず、別所でip_ctrl()やload_buffer_data()が清書され次第、スクラッチは丸ごと削除されることになります。


 またこのスクラッチは、当然ながら、最上位の関数から行わなくても問題ありません。スクラッチの対象は、作ろうとしている対象の直近のレイヤから、見通しの良いポイントを見定めて選定していきます。
 例えばきちんとモジュール性や凝集度・結合度を考えてソフトウェアを組んでいけば、モジュールのインターフェース部やライブラリのAPI部など、汎用性や機能の余裕が許容される節が生まれてくると思います。またあるいは事前の設計やデザインパターン等によって、実装像が明らかになっている箇所が出てくる場合もあるでしょう。スクラッチは、そうした実装に余裕がある箇所・実装が明快な場所を探して書いていきます。


 なおCの場合はスクラッチからTDDでの実装対象まで、何重もの関数呼び出しが挟まることがあるかもしれません。そうした場面では多層のスクラッチを書くことになります。が、真っ当な構造化プログラミングをやっていれば、関数は大抵ステートレスになり、また関数の呼び出し構造はピラミッド構造(上位の関数が下位の関数を呼ぶ)か、ひし形構造(上位の関数が共有関数を呼ぶ)か、そのハイブリッドの形態を取るため、関数のイメージに誤差が出るといった問題にはあまりつながらないと感じます。

実装のV字モデル

 このスクラッチコーディングの肝は以下になります。

  1. 上位レイヤの実装イメージを具体化する
  2. 次にTDDで実装しようとする下位レイヤに対して、要求されているもの、されていないものを明らかにする。

 なおスクラッチは、ある程度設計や分析で代替することも可能でしょう。

 とりあえずイメージとして、設計やスクラッチ等を使ってトップダウン(アウトサイドイン)で目標を定め、ボトムアップ(インサイドアウト)でTDDを進めるような、V字の実装手順を取ることになります(個人的に実装のV字モデルなどと呼んでいます)。メリットとしては以下があります。

  • スクラッチ等で明確化されるレイヤに対して、コードが最適化される。
  • 実装している関数の使い方・必要機能をイメージしやすくなる。
  • スクラッチ等で明確化されるレイヤの可読性が上がる。(例えばスクラッチで使った命名や構造を意識して製品コードを書けば、スクラッチと同程度の読みやすさが実現されます)

 スクラッチコーディングはあくまで書き捨てであり、効率が良いとは必ずしもいえないので、常用は難しいかもしれません。ただ困ったときに使えるツールの1つにはなるでしょうし、インサイドアウト開発一辺倒になりがちな組込みでのテスト駆動開発、C言語でのテスト駆動開発では、役に立つ場面は結構多いと感じます。