- オブジェクト指向ソフトウェアでは適切なクラスを見つけるのが重要
- 絶対確実な方法はない
- 基本的な指針やアイディアを提供することは可能
- ただし、創造性が要求されるので、最終的には才能と経験と運が必要
- 方法論を説明することの第一の役割
- 啓蒙的な先駆者に注目
- すでに分かっている落とし穴を回避
- 再利用は有用
- 手法と呼べるもの見出すためには、選択という技術が以下の二つの要素によって定義されるということを認識するのが重要
- 何については考慮するか
- 何については除外するか
クラスを見つけるという問題を理解するためには、広く公表されているアプローチを評価することから始めるのが良いかもしれない
- 要求仕様書内の名詞に下線を引く、という単純な規則が勧められることがある
- 「名詞はオブジェクトを表す」
- 例: The elevator will close its door before it moves to another floor.
- 名詞: elevator, door, floor
- このやり方ではあまり多くを期待できない
- 自然言語では、微妙なニュアンスの違い、個人による違い、あいまいさを免れない
- 書いた人の個人的なスタイルに影響されているような文書に基づいて重要な決断をするのは危険
- この手法で見つかるクラスは、もともと明白なものなので見つけるのが難しくないものが多い
- それ以外のクラスを見つけるためには、ほとんど役に立たない
- 他のより良い手法の引き立て役としては使うことができる
- 要求仕様書中の名詞が最終的な設計のクラスになることもあるが、同時に多くの「虚報」も含まれている
- 例: エレベータシステムに__DOOR__クラスは必要か?
- 必要かもしれないし、必要ではないかもしれない
- open or closeの二択以外が不要なら__ELEVATOR__クラスの属性で十分
- 以下の抽象データ型理論に関わる質問が重要
- 「ドア」は明らかに独自のものとして識別される操作を伴う独立したデータ型か、あるいは、ドアに関するすべての操作はすでに__ELEVATOR__などのほかのデータ型に対する操作に含まれているか?
- 答えを教えてくれるのは、設計者としての直感と経験だけ
- 要求仕様書は参考になるが、文法的な基準が表面的な助け以上になることは期待できない
- 抽象データ型理論に立ち戻れば、顧客や将来のユーザに対して正しい質問をする助けになる
- 必要かもしれないし、必要ではないかもしれない
- 例: 前章の__コマンド__と__操作__の話
- 両方とも記述中には出現していた
- ただしクラスになったのは__COMMAND__だけ
- 要求仕様書の分析では、両者に著しい違いを見出すことはできない
- floorの例
- 階数はエレベータシステムにとって重要なデータ抽象の一つ
- では__FLOOR__クラスは必要かというと、必ずしもそうではない
- 理由: 何階かという属性は、整数を使えば表現可能だから
- 以下のように整数とは異なる独自の操作が必要になるなら、クラス化するのは妥当
- 特定の階にいける人のアクセス権を管理したい
- 整数よりも利用できる操作を制限したい (ex. 二つの階の掛け算に意味はない)
- 「しかし、設計者はその目的のために本当に新しいクラスを増やしても良いものかどうか自問しなければならない」
- 抽象データ型の話
- クラスの対応物は、単純な意味での物理的な「オブジェクト(もの)」ではなく、抽象データ型
- システム分析の目的は「実世界をそのままモデル化すること」ではない
- 分析の役割は、世界の中の、検討中あるいは構築中のソフトウェアにとって意味のある部分をモデル化することである
- わがままの原則 (Principle of elfishness)
- 「そのオブジェクトによってできることを定義することによってのみオブジェクトは定義される」
- あるオブジェクトのある側面がどれだけ興味深くても、対象システムの目的に無関係なら、分析の結果に入れるべきではない
- 例えば建築家のためのCADシステムには__FLOOR__クラスが含まれるであろう
名詞にはクラスにならない概念を示唆する可能性がある一方で、絶対にクラスにするべきいくつかの概念を示唆できない可能性がある
そのような事故には少なくとも三つの原因がある:
- 人間の言語の柔軟性とあいまいさ
- エレベータシステムを説明する(意味的には等しいが表現が異なる)二つの要求仕様書の例
- 前者の記述では__移動__という重要な概念を見過ごしてしまう可能性がある
- いくつかの重要な抽象が要求仕様から直接導き出せない
- 例: パネル駆動式システムでの__状態__と__アプリケーション__というような抽象
- 重要だが要求仕様書では明示されていない可能性も高い
- テキストエディタでのコマンド抽象(ex. 行の挿入と行の削除)も同様
- ソフトウェアのクラスに対応する外界のオブジェクトが存在しない例
- 再利用を見過ごしている (これは分析の基本として要求仕様書を使うすべての手法に共通)
- OOPの多くの文献で、以下のようなソフトウェア開発の伝統的な見方が当然のこととして認められている
- (確定した)要求仕様書 => 解決策を考える
- オブジェクト技術の重要な教訓の1つは問題と解決策の間に明確な区別がないということ
- 既存のソフトウェアが新しい開発に影響を与えることは可能であるし、そうあるべき
- ex. それまでの開発や利用可能なライブラリに関する知識を要求仕様にフィードバック
- ex. 必要であれば、ユーザの関心が限られている機能を削除 (してより再利用性を上げる)
- 「対応する抽象は、新しいプロジェクトの要求仕様書の中ではなく、既存のソフトウェアの中に見つかる可能性が最も高い」
正しい抽象を見つける方法は頭を悩ませながらテキストエディタの要求仕様書とにらめっこすることではない。 知的発見(いわゆる「ユリイカ」、そのための確実な方法は存在しない)の過程で見出すか、 あるいは、誰かがすでに解決策を見つけている場合には、その人の抽象を再利用するかである。 もちろん、何らかのライブラリに含まれているのなら、対応する実装を再利用することもできる。 すべての分析-設計-実装という作業をやってもらえているわけだから、その方がさらに良い。
- 冒頭の引用文
- 発明には「さまざまな組み合わせを作る」ことと「その中から重要なものを選び認識する」ことの二つが必要
- 天才とは二番目の作業を進んでする人
これまでの検討で得られた、単純な教訓と、いくつかのあまり気づかれない結論:
- 単純な教訓
- 要求仕様書を信用し過ぎるな、文法的な基準は一切信用するな
- 「虚報」の件から教訓
- クラスと見つける基準が必要なのと同様に、クラス候補を拒否するための基準も必要
- 著者が読んだOOPの分析と設計の本では、この作業に関する説明がほとんど載っていなくて驚いた
拒否することは重大:
- メンバーに情熱があるなら、クラスにしたい概念がなくて困るということはない
- 問題は、そうした大量の概念の激流がプロジェクト自体の決壊を招かないように流れを堰き止める事
- 候補の発見と、その中から不適切なものを除く、という両方の作業を絶えず行う必要がある
クラス誘出 (Class Elimination) の原則
------------------------------------
クラス誘出はクラスの提案とクラスの拒否という2つの仮定である。
これ以降、この章ではクラス誘出の過程の両方の要素について検討する
- 「探索の道しるべとして拒否の部分から始めるのが良いだろう」
- ここで扱うのは「助言的な否定」
これから説明する危険信号の多くは、ある1つの最も一般的で最も被害の大きい過ちに至る。 その過ちとは、最も明らかな過ちでもあるが、すなわち、クラスではないクラスを設計することである。
- モジュールを__class__...feature...__end__と書けば、それで真の意味のクラスになるわけではない
- 実は単なるルーチンかもしれない
- 意味のあるデータ抽象に対するかを確認する必要がある
- 以降では、そのクラスが「偉大なる過ち」であるかもしれない危険を知らせる典型的な特徴をいくつか紹介する
- クラスの役割を聞いたとして、その答えが「このクラスは・・・する」の場合は怪しい
- 通常は設計の欠陥を表している
- クラスは何か1つのことをするものではない
- ほかの何らかのクラスの1ルーチンにすべきものであった可能性が高い
- 特定の型のオブジェクトに対していくつかのサービス(特性)を提供するもの
- クラスは適切だが、その表現方法が操作に偏っているだけ、という可能性もある (NeXTの例)
- 動詞の命令形あるいは不定詞を名前とするようなクラスは「1つのことをする」クラスのケースの可能性がある
- ex: PARSE, PRINT
- クラスが正しい場合もあるが、その時には名前を変えるべき
クラス名に関する「絶対的肯定」の規則:
クラス名の規則
-------------
クラス名は常に次のいずれかでなければならない。
- 名詞。修飾されていても良い。
- (構造上の属性を表す暫定クラスの場合のみ)形容詞。
- 「この規則はある意味で約束事の一種であるが、クラスはすべてのデータ抽象を表すという原則を強化する」
- 一番目のケースの例: LIST, LINKED_LIST
- 二番目のケースの例: COMPARABLE (ref: 24.5.11 構造継承)
その他:
- 英語のほぼ全ての動詞は名詞化できる云々
- 要求仕様書からクラス名(名詞)を抜き出す手法との対比
- エクスポートされたルーチンを1つだけ含む有効クラスも「偉大なる過ち」の典型的な兆候の1つ
- おそらくオブジェクト指向的な分解ではなく、機能的な分解の一単位で、実際以上にりっぱいに見せられたサブルーチンに過ぎない
例外:
- 抽象化された動作を表す正当なオブジェクト
- ex:
- 対話式システムにおけるコマンド
- 関数オブジェクト
- ただし、そのようなケースについても通常はいくつかの適用可能な特性がある
- 例: 数学の積分される関数を表すソフトウェアオブジェクト (ref: 21.6.2 小さいクラス)
- ex:
- 継承のケース
- 祖先で定義されている意味のある抽象への追加の場合は、クラステキストの中にエクスポートされたルーチンが1つだけしか宣言されていないのは、必ずしも悪いことではない
- しかし、分類マニアという継承に関係する病いのケースに通じることもある (ref: 24.4 分類マニア)
分類マニアの話から気をつけてほしいのが、あまりにも早い段階から継承の階層構造について心配してしまうという初心者が陥りやすいもう1つの間違いである
- 継承はオブジェクト指向における中心であり、良い継承構造は設計の品質において重要
- しかし、
- 継承が1つの関係として認められるのは良く理解されている抽象の中においてだけ
- まだ抽象を探し求めているときに継承の階層構造を考えるのは早すぎる
- 一旦抽象が定まれば、その後はクラス誘出と継承構造設計は互いにフィードバックし続ける
唯一の例外:
- すでに存在している分類法が広く受け入れられている応用分野を扱う場合
- 例えば科学のいくつかの分野では、良く認識され安定した抽象と継承構造がともに存在する
- 全くルーチンを含まないクラスや、クエリはあるがコマンドがないクラスに出会うことがある
- そのようなクラスはCの構造体に当たるものであり、設計上の誤りを示している可能性がある
以下の三つのケースの場合は、クラス設計は適当:
- オブジェクト指向ソフトウェが変更することができない外界から獲得されるオブジェクトを表現している場合
- ex. センサデータ、パケット交換網からのパケット、外部ライブラリが提供するCの構造体の対応物
- 直接インスタンス化することが目的ではない場合
- 継承によってほかのクラスが使うために定数などの共有性質をカプセル化するために作られたクラス
- 共有性質継承 (facility inheritance)、詳細は継承の方法論の章で
- 適用的(applicative)なクラスの場合
- __INTEGER__や__REAL__は自分自身は修正せずに、関数の返り値で新しいオブジェクトを生成して返す
- 意味的にはコマンド関数と同様
- 関数型言語(特に破壊的な操作を許さない言語)ではよく見かけるオブジェクト
対して、以下の二つのケースは設計の欠陥を示している:
- クラスの存在は正しいが、コマンドが見当たらない場合
- 対応するオブジェクトを修正するメカニズムを用意するのを単に設計者が忘れてしまった
- 23.2.6で紹介するチェックリストは、このような間違いを防ぐのに役に立つ
- クラスの存在が正しくなかった場合
- そのクラスは真のデータ抽象ではない
- ex. リストや配列などの構造によって表現可能
- ex. ほかのクラスに属性を追加するだけで表現可能(な受動的な情報)
- このようなケースは開発者がPasTcal(or Ada,C)の単純なレコード型であったものの代わりにクラスを記述するときに起こることがある
- 全てのレコード型が独立したデータ抽象になるわけではない
- 現在あるいは将来的に正当なクラスになる余地があるかを、注意深く検討する必要がある
- その答えがあいまいならば、たとえやりすぎになる危険性があったとしても、とりあえずそのクラスは取っておいたほうが良いかもしれない
- 後で導入するのは大変かもしれない (ISEコンパイラの例)
不完全な設計を示すもう1つの兆候は、同じクラスに含まれる特性が複数の抽象に関連する場合である
NeXTライブラリの例
connasecence:
- 片方への変更が他方への変更を意味するような同時発生的な変更を基準として2つの特性が密接に関係していることを示す言葉
- クラスライブラリ間のconnasceneは最小限にとどめるべき
- ただし、1つのクラスの中に現れる特性はすべて同じ明確に識別された抽象に関連していなければならない
クラス一貫性の原則
-----------------
1つのクラスに含まれる特性はすべて1つの良く識別された抽象に関係していなければならない。
考え得る間違いの復習をしたことで、反対に、理想的なクラスとはどのようなものであるかがはっきりしてくる
理想的なクラスの典型的な特徴:
- データ抽象として表すことのできる明確に関係付けられた抽象が1つある
- クラス名が名詞か形容詞で、その抽象の特徴を適切に表す名前である
- クラスが実行時オブジェクトとなる可能性のあるもの、すなわち、インスタンスの集合を表している
- シングルトンも許される
- インスタンスの属性を調べるためのいくつかのクエリが用意されている
- インスタンスの状態を変えるためのいくつかのコマンドが用意されている
- 整数のような適用的なクラスでもOK
- 抽象的な属性として次の事柄を非形式的あるいは(なるべくなら)形式的に記述することができる
- クラス不変表明、事前条件、事後条件
このリストは非形式的な目標を表しているのであり、厳密な規則を表すのではない。 正当なクラスでもこの中の性質のいくつかを満たしているだけかもしれない。 とはいえ本書で重要な役割を果たす例のほとんどはすべての特質を満たしている。