新訳版『テスト駆動開発』に学ぶオブジェクト指向設計

テスト駆動開発テスト駆動開発

和田卓人(t-wada)さんによる『テスト駆動開発』の新訳版が出版されました。

オブジェクト指向でソフトウェアを開発するのであれば、この本とマーチンファウラーの『リファクタリング』は必読書だと思います。この古典ともいえる『テスト駆動開発』が和田さんの手によって新訳版として復刊されたことは、ほんとうにすばらしいことです。

この本が出版された経緯と、和田さんはじめ関係者の方々のご努力については、和田さんの、このブログをぜひ読んでいただければと思います。
新訳版『テスト駆動開発』が出ます

新訳本は、単に原著が日本語で読めるようになっただけではありません。和田さんの手によって、原著にはない新たな価値が付け加えらました。

一つは、サンプルコードの工夫です。

  • できるだけ省略はしない
  • 変更箇所を目立つようにした
  • 各章末にその時点での全コードを記載する

これらの工夫により、本に書かれた内容が、より具体的に理解がしやすくなりました。また、実際に手を動かしてみるときに、迷子になりそうな個所も一掃されました。大勢の技術者にテスト駆動開発を伝える活動をされてきた和田さんの知見とノウハウ、思いが、このサンプルコードの工夫に結びついているのだと思います。

もう一つすばらしい内容が、付録Cとして追加された訳者の和田さん自身の解説「テスト駆動開発の現在」です。テスト駆動開発というムーブメントの歴史を振り返りつつ、現在の状況と、これからの展望が、わかりやすくまとめらています。
「テスト駆動開発の現在」で和田さんが書かれているように、いろいろな意味で状況が変わった現在こそ、もう一度、テスト駆動開発の本来の意味を考え、何が大事なことなのかを改めて理解して手に入れる好機です。新訳版『テスト駆動開発』は、和田さんのこの解説により、原著以上の今日的な新しい価値が加わりました。

この「テスト駆動開発」の内容を、オブジェクト指向の設計の考え方とやり方に焦点をあてながら、簡単に紹介したいと思います。
本の帯にも書かれているように、「TDDはテスト技法ではない。分析技法であり、設計技法であり、開発のすべてのアクティビティを構造化する技法」なのです。

テスト駆動開発の2つのシンプルなルール


  • 自動化されたテストが失敗したときのみ、新しいコードを書く
  • 重複を除去する
(「まえがき」から)

一つ目のルールは、テスト駆動開発の基本として多くの人が理解し実践していると思います。しかし、2番目のルールはどうでしょうか? 「オブジェクト指向の設計」という観点からは、この2番目のルールが重要な意味を持ちます。
コードの重複を排除することで、コードを読みやすくし、変更があった時の影響を狭い範囲に閉じ込めるのが、オブジェクト指向で設計する目的です。
TDDの基本サイクルは、レッド・グリーン・リファクタリングです。リファクタリングを繰り返しながら「重複を除去する」活動こそがTDDの本質です。

この本のサンプルを読み進めるときには、この2番目のルール「重複を除去する」ために、どのようなリファクタリングを行っていくかに注目してみてください。オブジェクト指向の設計では、どのようにコードの重複を除去するのかを、手を動かしながら体験できるのが、この本のすばらしい点です。

開発活動の目的と背景を理解する


ソフトウェア開発には、背景と目的があり、それぞれの置かれた状況があります。この本の最初のサンプルである「多国通貨」がなぜ必要になったのか、「多国通貨」にどのようなビジネス価値があり、変更の対象となるコードはどのような状況にあり、開発者たちのどのような経験を積み重ねてきているか。

そういう目的や背景が「はじめに」で説明されています。サンプルコードに飛び込む前に、この「はじめに」を熟読すべきです。ソフトウェア開発とは、こういう目的と背景の中で、積み重ねてきた経験や技術資産の延長として行うものです。TDDを取り入れるということは、こういう目的・背景・状況をよく理解することが前提だということです。

ビジネス価値については、技術者が直接コントロールすることはできません。しかし、スキルを身につけること、コードの整理やテストの整備は、技術者自身がコントロールすべきことがらです。一朝一夕にできることではありませんが、日々の開発の中で、少しずつコードの整理とテストの整備を積み重ね、そのためのスキルを磨いてていく活動を進めていくことがTDDの基本です。

ビューを使って仕様をざくっと理解する


第1章「仮実装」の冒頭で、レポート機能のサンプルが提示されています。レポートや画面のように、利用者が目にする情報(ビュー)は、要件を分析し、理解するためのわかりやすい手がかりです。
このレポートの出力例を元にして、何をすべきかをTODOとして書き出していく。これが「分析」活動です。

簡単な例ですが、TDDの考えるソフトウェア開発の基本の流れが明示されています。コードを書く人間が、利用者が使うレポートを参考にしながら、どのような仕様を満たすべきかを考える、ということです。

ここには、仕様を誰かから教えてもらう、という行動はしていません。このチームが、このプロダクトの開発に何年もかかわってきて、この問題領域(ドメイン)について、かなりの知識を持っているという背景もあります。しかし、開発者にとっても「多国通貨」という領域は未知の領域だったはずです。
それにもかかわらず、開発者たちは仕様について、自ら考えながら、TODOリストを書きだしています。この開発スタイルがTDDの基本です。仕様は、開発者が能動的に考える。これは、ドメイン駆動設計の根底にある考え方と共通です。

モジュール化の方針


要求仕様はレポート機能の分析から開始しています。しかし、書こうとしているプログラムの単位は、「レポート機能」とか「レポートサービス」ではありません。レートを換算するための RateConverterクラスとか、RateConversionService を書こうとはしていません。

そうではなく、最初に作成するクラスは、Dollarクラス(Dollar.java)です。

「換算する」という機能に注目するのではなく、換算するためには、DollarクラスとCHF(スイスフラン)クラスという単位でプログラムを書こうという発想です。

機能に注目するか、換算の対象であるDollarやCHFに注目するか。クラスの設計方針の大きな分かれ道です。

「換算する」という機能に注目して、それをそのままプログラミングの単位として手続き(サブルーチン)に分解しようというのが、手続き型のアプローチです。データを持つクラスと処理を持つクラスを分け、トランザクションスクリプトを開発するスタイルです。

それに対し、ドルやスイスフランという関心事の単位にプログラムを分割する発想が、オブジェクト指向のスタイルです。
「換算」という機能よりも、もっと基本的な部品である「ドル」や「スイスフラン」をどうオブジェクトとして表現するかからスタートしています。問題領域の関心事をオブジェクトで表現するという典型的なドメインモデルの開発スタイルです。

手続きとしてトップダウンにプログラム単位を分割していくか、何を扱いたいかの具体的な対象に注目して、ボトムアップに部品を用意していくか。この二つのアプローチの違いは、ソフトウェアの設計方針の大きな分かれ道です。

そして『テスト駆動開発』では、当たり前のように、Dollar.java からスタートしています。第1章「仮実装」で、ここからスタートしていることは、オブジェクト指向の設計という観点からは、ほんとうに味わい深い内容です。
レポート機能とその出力サンプルを分析しながら、プログラミング単位は、機能単位や出力のビュー単位ではありません。問題領域の具体的な関心事である、「ドル」と「スイスフラン」をオブジェクトとして表現することから手を付けています。

最終的には換算機能を実現するわけですが、そこに近づくために、まず、基本的な部品から手をつけるという、このボトムアップのスタイルこそ、オブジェクト指向らしい設計スタイルです。

クラスの粒度、ユニットテストの単位


第1章「仮実装」から第16章「将来の読み手を考えたテスト」までは、レポート機能に多国通貨に対応するための部品となるオブジェクトを、発見し、成長させていく過程が具体的に描かれています。実施に手を動かしながら、その過程をぜひ体験してみてください。
そして、その時にTDDの2番目の基本ルール「重複を除去する」活動に、注目してください。まどろっこしいような手順を踏んでいる理由のいくつかは、この「重複を除去する」という活動を例示するためだ、ということが体感できるはずです。

16章までのプロセスと成果物の振り返りが、第17章「多国通貨全体ふりかえり」です。
この章で注目したいのは、136ページの「コードメトリクス」です。

クラス数


プロダクトコードのクラス数は5です。テストコードのクラス数は1です。
つまり、一つのユニットテストは、ひとつのクラスではなく、5つのクラスを対象にしています。5つのクラスは、moneyパッケージとしてまとめられています。テストの対象の「ユニット」は、個々のクラスではなく、moneyパッケージです。

このテストクラス数とプロダクションのクラス数の比率は、オブジェクト指向で設計する場合のおおまかな粒度の指針として参考になります。ひとかたまりのユニットとしてテストする内容は、5つくらいのクラスを組み合わせた処理になる、という比率です。一つのプロダクションクラスに一つのユニットテストというのは、おそらく、プロダクションのクラスの粒度が大きすぎます。
関心事を小さなクラスに分けて、それを組み合せる、というのがオブジェクト指向設計の基本です。また、パッケージが、テストの単位という点もパッケージ設計やテスト設計の指針になります。

第16章の章末に記載された次の5つのクラスへ分割する設計をぜひ味わってみてください。
  • Money.java
  • Sum.java
  • Expression.java
  • Bank.java
  • Pair.java

まだ、開発途中ということもありますが、いちばん大きなMoneyクラスでも100行未満です。通貨の組み合わせを表現するために、Pairという10行程度のクラスを導入しています。

こういう用途をより具体的にした小さなクラスを部品として用意する、というオブジェクト指向らしい設計スタイルを、この本を通じて体験することができます。小さなクラスに分けて組み合わせることが、コードを整理し、重複をなくし、その結果、変更が楽で安全になります。そういうオブジェクト指向らしい設計スタイルとその効果が、この多国通貨の例から実感できます。

他のメトリクスも見てみましょう。

メソッドの行数


プロダクションコードでは、4.5行/メソッドです。メソッドの宣言と中括弧もカウントしていますから、実質的には、3行未満ということです。これは、開発途中だから、ということではありません。「重複を除去する」を徹底すると、自然に、こういう短いメソッドが増えるということです。

テストコードは、7.8行/メソッドです。これは、表の注釈にあるように、テストコードのほうは「重複を除去しきれていない」からです。

TDDの2番目のルール「重複を除去する」をどの程度、実践できているかは、このメソッドあたりの行数から推測することが可能です。

オブジェクト指向の設計で、コードの重複を除去する活動の結果は、メソッドの行数としてある程度は観測できるということです。

循環的複雑度


この尺度は、簡単に言えば、if文の分岐が多いか少ないかの指標です。
テストコードは、1.0。つまり、if文(条件分岐)がまったくないということです。

そしてプロダクションコードでも、なんと、1.05です。プロダクションコードでもif文(条件分岐)がほとんど登場していない、ということです。

if文は、複雑さの根源です。オブジェクト指向で設計するということは、この複雑さを取り除くことも重要な目標です。サンプルコードを読みながら、どのように、条件分岐の複雑さを除去していっているかをぜひ手を動かしながら味わってみてください。途中で継承が登場しますが、重複を徹底的に除去していく過程で、最終的には、継承を取り除くリファクタリングは、なかなか味わい深いものがあります。



『テスト駆動開発』の第一部の「多国通貨」の例について、オブジェクト指向設計という観点から、本の内容を簡単に紹介してみました。この本は、読み物というより、手を動かして体験学習をするための教材なんだと思います。ぜひ、実際にコードを書きながら、コードの変化していく様子と、なぜ、そうするのか、何が変わるのか、を味わってみてください。

オブジェクト指向設計のスキルが一段階あがることはまちがいありません。

calendar
     12
3456789
10111213141516
17181920212223
24252627282930
31      
<< March 2024 >>
システム設計日記を検索
プロフィール
リンク
システム開発日記(実装編)
有限会社 システム設計
twitter @masuda220
selected entries
recent comment
recent trackback
categories
archives
others
mobile
qrcode
powered
無料ブログ作成サービス JUGEM