モデル推敲に有効な手法を紹介する良い設計モデルの作り方(後編)

» 2004年04月27日 12時00分 公開
[後藤啓,ボーランド]

 前回(「正しい設計と理想的なモデル」)は、「良い設計モデルとは何を意味するのか?」「どうすれば良い設計モデルを作ることができるのか?」について、概念的な話をさせていただきました。何となくイメージしていただけたでしょうか? 簡単にいってしまうと「できるだけ、分かりやすくて、拡張性や保守性がある、開発しやすいモデル」が「良いモデル」であって、そのモデルを作るために、「オブジェクト指向技術の抽象化やカプセル化をうまく使って、積極的に依存性を排除し(モジュール間の独立性を高め)、シンプルなモデルに仕上げていきましょう」という話になります。しかし、実際は、「言うは易(やす)く行うは難(かた)し」です。今回は、「どうすれば、良い設計モデルに近づけるか?」という具体的な話をしていきたいと思います。

「分析モデル」から「設計モデル」へ

 前回解説した「CRC分析法」や「名詞・動詞分析法」、もしくはドメイン・ウォークスルーといった方法により、分析モデルを作成します。この段階では、どういった登場人物(クラス)があり、それぞれがどのように関連し、どういった責任(オペレーションや属性)を持っているかを中心に、大まかなクラス構成を作成します(通常、「ユースケース実現」やそのほかの分析方法によって、分析モデルを推敲するのですが、その方法は、割愛します)。

 次に、この分析モデルをインプットとして、設計モデルを作成するのですが、ある程度分析モデルは正しく作成されている、つまり、すでに分析モデルの段階でモデル自体が機能要求を満たしていない、または責任が正しく分割されていない(手続き指向のモジュール分割になっていて、巨大なコントロール・クラスがいくつも出来上がってしまっていた)状態ではないとします。まずは、単純に設計クラスに置き換えていきます(実際のクラス名や属性名にマッピングしたり、Javaのパッケージ分割を行ったりするわけです)。

 その後、再度ユースケース実現によって実際のオペレーションの確認を行い、関連の誘導方向性等を確認していきます。この段階で、共通部分や依存性の制御のために抽象化を行い、インターフェイスを定義したり、デザイン・パターンに当てはめたりして、設計モデルを推敲していきます。

 また、開発プロセスによっては、この段階から機能ごとの設計に入っていく場合がありますが、その場合には、抽象化自体は新しい機能を追加する段において、リファクタリングにより推敲が行われていく場合もありますし、前回の話のように明示的にこういったフェイズを設けないプロセスもあります。

 では、この推敲段階(良い設計モデルに近づけていく)において、有効な手法をいくつか紹介させていただきたいと思います。

ソフトウェア・メトリックス

 ソフトウェア・メトリックスはかなり古い段階から、いろいろな測定方法が研究されています。メトリックスとは、基本的にはソース・コードを解析してその構造を測定することですが、オブジェクト指向パラダイムにおいては、実装コードの状態に加え、モデル構造(個々のクラスの構造やクラス間の関係など)に着目して測定する方法も発表されています。従って、こういったメトリックスをガイドラインにしながら、設計を推敲していくことも有効方法と考えられます。

 ただ、これらのメトリックスは、現段階ではその効果が完全に実証された存在ではなく、閾値(Thresholds)に関しても、それぞれの研究者やコンサルタントがいろいろな値を推奨していますが、これらはあくまで経験によるガイドライン値という位置付けで、「この値を超えているから、即、設計をし直す必要がある」とか「悪い設計である」とはなりません。これらの値は、システムのアーキテクチャや実装言語に依存する場合もありますし、ある意味を持って明示的に構造を変えている場合(パフォーマンスや、依存性の制御のためなど)もあるからです。重要なのは、「何を測定しようとしているか」と「その値が大きくなる、または小さくなると、どういった傾向があるか」という点を理解し、「その値に近づけることにより、どういったメリットがあるか」を考えながら、ガイドラインとして使用していくということだと思います。

 今回は、これらのメトリックスの中で、ChidamberとC.Kemererが『A Metrics Suite for Object Oriented Design.』(IEEE Transactions on Software Engineering, Vol. 20 Number 6, pp.476-493 1994)で発表したメトリックスをご紹介します。このスイートとして発表された6個のメトリックスは、その後さまざまに再考されていますし、詳細に解説することは避けます。本稿では、そのバックグラウンドとして、どういった意味があるかを中心に紹介いたします。

WMC - Weight Methods per Class


定義:各クラスの全メソッドの複雑さの総和


 基本的には、クラス自体がどれだけ複雑になっているかを測定するメトリックスです。当然クラスが複雑になれば、そのクラスを開発または保守する場合の時間や労力が必要になります。また、クラスが複雑になるということは、正しく責任の分割が行われていない可能性がありますし、よりアプリケーション色が強い傾向にあり、再利用がしにくくなる傾向にあります。先ほども述べましたが、非常に大きな(複雑な)コントロール・クラスがいくつも存在する場合は、オブジェクト指向本来の、「オブジェクト間のメッセージのやりとりによってシステムが動くように設計する』に反しているわけで、より手続き指向的なアプローチになっている可能性があります。

 メソッド自体の「複雑度」というのは、上記の論文の中では明示的に定義していません。それは、メソッド自体の複雑性自体は従来のメトリックスにより測定可能と考えられているためで、通常よく使われるものとしては、T. McCabeによる「CC - Cyclomatic Complexity」*(1)や、単純にメソッドのパラメータの数を複雑度とするもののメソッドの中で、送られるメッセージの数に重みを付けて測定する方法などがあります*(2)。


*(1) 『A Complexity Measure.』(IEEE Transaction on Software Engineering, SE-2 No.4 December: pp.308-320, 1976)で言及されています

*(2) Mark LorenzとJeff Kiddの共著『Object-Oriented Software Metrics』 (Prentice Hall,1994)で紹介されているメソッドの中に、送られるメッセージの数に重みを付けて測定する方法があります

DIT - Depth of Inheritance Tree


定義:それぞれのクラスにおける継承度の深さ


 継承によりプログラムを再利用することは有用なのですが、継承は最も強い依存関係を生みます。従って、継承が深ければ深いほど、そのクラスに影響を与えるクラスが多く存在することになります。また、継承が深いほど、そのクラスが持つメソッドが多くなり(上位のメソッドを受け継ぐため)、そのクラスを理解することが難しくなります。

 システム全体の平均を取ることにより、DITが低ければ、そのシステムがあまり継承を使用せず上位過多(Top heavy)の傾向にあり、DITが高ければ、システムが継承を多用している下位過多(Bottom heavy)の傾向にあると観察することができます。

 オブジェクト指向技術の初期のころは、継承によるプログラムの再利用が比較的重要視されていましたが、最近では継承よりも、インターフェイスによる抽象化の有用性が認められています。この有用性については、Peter Coadによる『Java Design: Building Better Apps & Applets, Second Edition.』(Prentice Hall PTR, 1999)に詳しく述べられていますので、ご興味のある方は、ぜひご参照ください。

NOC - Number of Children


定義:それぞれのクラス直下のサブ・クラス(チャイルド・クラス)の数


 基本的には、NOCが高ければ、継承による再利用がなされているわけですが、直系のサブ・クラスが非常に多い場合は、抽象化が正しく行われていない可能性もあります。また、NOCが高いということは、そのクラスを再利用しているクラスが多いというわけなので、そのクラスのテストが煩雑になる傾向がありますし、間違って再利用しているケースも増えてきます。

継承は最も高い依存性を生むため、NOCが高いクラスは、そのクラスを変更することにより影響を受けるクラスが多いともいえるので、変更する場合には影響力を考慮する必要があります。

CBO - Coupling between Object Classes


定義:それぞれのクラスと結合しているクラスの総和


 結合しているクラスとは、ターゲットのクラスが、属性、パラメータ、戻り値、例外、ローカル変数として使用している型と、その型の属性やメソッドの呼び出しで使用されている型を含みます。また、この総和の中には、同一の型は1回のみカウントされ、継承元のクラスやプリミティブ型は含まれません。

 CBOの値が高い、つまり、オブジェクト間の結合度が高いということは、モジュール設計がうまく行われておらず、クラスの再利用性を低くしている可能性があります。また、結合の数が多くなればなるほど、ほか(結合しているクラス)の変更に対し、影響を受けやすくなり、メンテナンス性の低下にもつながります。この値は、そのクラスがどれだけ複雑なのかを知るためにも有用です。内部で結合しているクラスが多いほど、よりテストが過酷なものとなる傾向があります。

RFC - Response for a Class


定義:それぞれのクラスに対して呼び出し可能なメソッドの総和


 RFCもWMCと同様に、そのクラスの複雑度を示します。WMCは各メソッドの複雑度の総和になりますが、RFCは単純にメソッドの数になります。ただし、RFCは継承によるメソッドの総和になるため、継承関係が深いものや、継承元のクラスに多数メソッドがある場合、そのクラスのRFCは高くなります。

 RFCが高いクラスは当然複雑になるわけで、テストやデバッグが複雑になる傾向にありますし、必要なテストの数や時間も多くかかる傾向にあります。ただし、RFCが1、つまり、継承がまったくなく、そのクラスに1つだけしかメソッドのないクラスが非常に多い場合というのは、オブジェクト指向に従って正しく責任が分割されているとはいい難いアーキテクチャになっている場合があることにも注意してください。

LCOM - Lack of Cohesion in Methods


定義:それぞれのクラスにおける、属性によるメソッドの不同性(凝集度の欠如)


 少し難しいいい回しですが、つまり「それぞれのメソッドが、いかに均等にそれぞれの属性にアクセスしているか?」ということです。詳細は述べませんが、LCOMにはこの論文で定義されている測定方法以外に、Brian Henderson-Sellersによる『Object-Oriented Metrics: Measures of Complexity』(Prentice Hall, 1995)で、この論文のLCOMの欠点を補う形で発表されています。

 例えば、特定のセットの属性は特定のメソッドの中でアクセスされ、その他の属性は、その他のメソッドでのみアクセスされている場合は、そのクラスを複数のクラスに分割することが可能ということになります。また、こういったクラスは、クラスの責任分割の段階でうまく分割されていない可能性もあります。

 以上、この論文で紹介されているメトリックスの紹介は終わりですが、それ以外にも、『MOOD』(Metrics for Object Oriented Design)として発表されているものや、『An Object Oriented Metrics Suite on Coupling.』の中で紹介されているメトリックスも、基本的には設計モデルを推敲する場合に有用なメトリックスを多く含んでいます。

 これらのメトリックスの基本的な考え方は、

  1. それぞれのクラスが正しく責任を分担しているか?
  2. 個々のクラスが複雑過ぎる構成になっていないか?
  3. それぞれのクラスの関連が複雑になっていないか?

という点だと思います。

デザイン・パターン

 デザイン・パターンについては、いろいろな書物や記事で紹介されていますので、ここでは、デザイン・パターン自体の紹介はしませんが、デザイン・パターンの中でも、「Abstract Factory Pattern」や「Visitor Pattern」「Adapter Pattern」をうまく使っていくと、いろいろな局面で依存性を排除することができ、独立性の高いモデルを構築することが可能となります。

 ここで、デザイン・パターンの使い方なのですが、何が何でもデザイン・パターンという考えは正しくありません。当然、デザイン・パターンは特定の問題を解決するために用いるもので、デザイン・パターンを無理やり使ってモデルを複雑にしてしまっては、全く意味がありません。

デザイン・レビュー

 実際の設計モデルをレビュー形式で検証するのも良い方法だと思います。実際に、コード・レビューは比較的行われているようですが、設計レベルのレビューをあまり行っていない場合もあるように思います。実際の設計モデルを、ユースケース実現やドメイン・ウォークスルーにより検証し、「責任の分担や依存性の制御が正しく行われているか?」「複雑過ぎるモデルになっていないか?」などをブレーンストーミングの形式で、論議するのは必要だと思います。

 ただし、こういったレビューで注意する点は、設計担当者の「弾劾裁判」になってはならないということです。「ここは、駄目だ」とか「これでは、正しく機能しない」という、否定的な意見ではなく、「こうすると、ここのクラスの複雑度が少なくなるのでは?」とか「ここで、このパターンを使用すると、ここの関連がかなり減少するのでは?」といった、建設的な意見が重要だと思います。また、だらだらと長い時間をかけるのも、意味がありません。あくまで、設計担当者が最終的な責任を持ち、レビュー自体は建設的な助言にとどめて、「宗教論争」にならないようにすべきだと思います。

リファクタリング

 リファクタリングの技法についても、詳細は省略しますが、最近では、ほとんどのIDEで、比較的安全にリファクタリングを行えるものが増えているので、昔のようにかなり気合を入れて、リファクタリング作業を決心する必要がなくなってきました。

 メトリックスの結果やデザイン・レビューにより、リファクタリングの必要があれば積極的にリファクタリングを掛け「良いモデル」にしていくことが重要だと思います。

開発全体の流れ

上記のような手法を使って開発を進める方法としては、

  1. 依存性、複雑さ、責任の分割に注意し設計を行う
  2. メトリックスやデザイン・レビューを行い、問題点や改良点を洗い出す
  3. その結果を基にリファクタリングを実行し、モデルを推敲する
  4. メトリックスやデザイン・レビューを行い、問題点が解消していることを確認する

こういった流れで実行していくとよいと思います。


 「正しい設計と理想的なモデル」と題して前半、後半の2回に分けてお話しさせていただきましたが、多少イメージしてもらえたでしょうか? 前回もお話ししましたが、「UMLを使ってドキュメントを書いている」とか「Javaを使ってシステムを開発」していることが「オブジェクト指向開発」でないことは、皆さんも十分にご承知だと思います。わざわざ「オブジェクト指向」という技術を使うことによって、保守性や拡張性に優れた開発しやすいシステムを構築することができる可能性があるのですから、それを使わない手はないですよね? 今回は、時間の関係でデザイン・パターンやリファクタリングについては詳細を割愛させていただきましたが、また機会があれば、この辺をもう少し掘り下げて、実際のモデルを使ったお話しをしてみたいと思います。

著者プロフィール

後藤啓(ごとうあきら)

 1990年Smalltalkに触れ、オブジェクト指向開発に興味を持ち、以降、C++による、OODB、CORBA関連の開発に従事、その後、J2EE関連のコンサルタントとして、多数のプロジェクトに参加。UMLやモデリングの重要性と、いかに効率よく開発を行うかを中心にコンサルティングに従事。


Copyright © ITmedia, Inc. All Rights Reserved.

注目のテーマ