A Philosophy of Software Design ch4 Modules Should Be Deep
Modules Should Be Deep
- modular design
- 開発者が一度に直面する複雑性を少なくする
- ソフトウェアの複雑性を御するうえで最重要なテクニック
- 開発者が一度に直面する複雑性を少なくする
- 本章はこの基本原則について述べる
Modular design
- ソフトウェアシステムはモジュールのコレクションに分解される
- モジュールとは
- 比較的独立した部品
- class, subsystem, service等
- 理想オブ理想: どのモジュールも、互いに完全に独立している
- 開発者は、あるモジュールに変更を加える際、他のモジュールのことを一切気にしなくてよい
- 現実はそうではない
- モジュール間には依存がある
- あるモジュールに変更を加えると、他のモジュールも追従して変更が必要になる
- 例: 関数のシグネチャが変更されたら、呼び出しにも変更が必要
- 依存にはいろいろな形がある
- 依存を管理するうえで、モジュールを2つに分けて考える
- インタフェース
- あるモジュールを他のモジュールから利用するうえで、知らなければならない情報
- what
- 実装
- インタフェースに準拠して実行されるコード
- how
- インタフェース
- 例: 平衡木データ構造
- 利用者に見えるのはinsert, remove, fetch等のみ
- 木の平衡を保つためになんやかんやしているはずだが、利用者には見えない
- 本書では、インタフェースと実装をもつ任意のコード単位はすべて「モジュール」ということにする
- 重要なのは、インタフェースが実装よりも単純であること
- モジュールが他に課す複雑性を最小化する
- モジュールに変更を加えても、インタフェースを変えない限り、他に影響を与えない
- インタフェースが単純なほうがチャンス多し
What's in an interface
- 「インタフェース」は、有形/無形の情報を含む
- 【補】OOP言語機構としての
interface
だけのことを言ってるのではない、ということ
- 【補】OOP言語機構としての
- 有形
- 無形
- ほとんどの場合、無形の情報は有形の情報よりも大きく複雑である
- インタフェースを明記する利点
- モジュールを利用するために必要な情報を正確に表す
- 「分からないことが分からない」がなくなる
- 【補】複雑性のうち最も深刻なもの
- 「分からないことが分からない」がなくなる
- モジュールを利用するために必要な情報を正確に表す
Abstractions
- ある実体から、重要でない詳細を省いて単純化したビュー
- 【補】「モデリング」もそうね
- modular programmingと密接に関わる
- インタフェースは、モジュールの機能の抽象化
- 重要でない実装の詳細が省かれる
- インタフェースは、モジュールの機能の抽象化
- 「重要でない」詳細をそぎ落とせば削ぎ落とすほどよい
- 抽象化がうまく行かない2つのケース
- 重要でない詳細を残してしまう
- 「認知の負荷」になる
- 重要な情報を削ぎ落としてしまう
- 「分からないことが分からない」になる
- false abstraction
- 重要でない詳細を残してしまう
- 抽象化の例: ファイルシステム
- 略
- 抽象化はなにもプログラミングに限った話ではない
- 例: 電子レンジ
- 利用者が意識するのは出力と時間のみ
- 電気をマイクロ波に変えてなんやらかんやら…というのを利用者は意識しない
- 他にもいろいろ
- 例: 電子レンジ
Deep modules
- モジュールのべき論
- モジュールの機能 = インタフェースの複雑性 x モジュールの深さ
- cf. 長方形の面積 = 幅 x 高さ
- モジュールの深さとはすなわち、実装の複雑性のうち、抽象化により隠蔽される部分
- 「深いモジュール」とは、インタフェースが単純で、実装の複雑性の多くの部分が隠蔽されているモジュール
- 好例: UnixのファイルI/O
- システムコールはたかだか5種類しかない
- open
- read
- write
- lseek
- close
- 何十万行にもわたる実装が隠蔽されている
- 実装は何年にも渡り大きく変わってきたが、その間インタフェースは変わっていない
- システムコールはたかだか5種類しかない
Shalow modules
- インタフェースが実装の複雑性を隠蔽していないもの
- 実装自体が単純な場合は避けられないことも
- 例: 連結リスト
- insert/deleteの実装自体が数行で終わるような代物
- 例: 連結リスト
- こういうのはやめよう:
private void addNullValueForAttribute(String attribute) { data.put(attribute, null); }
- 複雑性の見地からは、悪くなりこそすれ良くはならない
- 一切抽象化されていない
- シグネチャが実装をすべて語っている
- インタフェースの複雑性が実装のそれと変わらない
- まともにドキュメンテーションしたら実装より長くなるまである
data
を直接触ったほうがタイプ量少ない- 【所感】それは別によくない?
- 開発者が新しいインタフェースを覚えなければならないという点で、システムに複雑性を導入している
- しかしなんの利益ももたらさない
- 一切抽象化されていない
Classitis
- 「クラスは深くあるべき」論は現在のところ、広くは認められていない
- 「クラスは小さくあるべき」というのが主流
- 大きなクラスは小さなものに分割する
- N行より長いメソッドは分割する
- classitis症候群
- 「クラスは小さくあるべき」という考えの究極
- 各クラスの機能を最小にする
- 機能を追加するときはクラスを追加する
- 個々のクラスは単純だが、システム全体の複雑性を増してしまう
- インタフェースが増加するぶん
- 定形コードの冗長性も
Examples: Java and Unix I/O
- インタフェースは、普遍的なケースが可能な限り単純になるように設計する
- 【補】柔軟なものが使いやすいとは限らない
- 利用側が知らなければならないことが増える
- 利用の手順が増える
- ミスが発生しやすい
- 【補】GoFのFacadeパターンはこの考え方に基づいたもの
- 【補】柔軟なものが使いやすいとは限らない
- べき論に反している例: Javaでシリアライズされたオブジェクトをファイルから読み込む例
FileInputStream fileStream = new FileInputStream(fileName); BufferedInputStream bufferedStream = new BufferedInputStream(fileStream); ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);
- 2行目を忘れると、バッファリングが効かずI/Oは遅くなる
- 支持者の言い分
- 「利用者全員がバァファリングを利用したいとは限らない」
- 著者の意見
- ほとんどの利用者がバッファリングを使うのであれば、デフォルトで提供すべき
- オプションで無効化できるようにすればよい
- べき論に従っている例: Unix
- デフォルトでシーケンシャルアクセス提供
- ランダムアクセスしたくなったらlseekシステムコール
- シーケンシャルアクセスしか利用しなければ知る必要なし
Conclusion
- 実装からインタフェースを分離せよ
- 【補】SOLID原則のISP
- いらんものに依存しない
- 【補】SOLID原則のISP
- モジュール利用側は、インタフェースにより提供される「抽象」にのみ依存する
- 実装の複雑性を意識しない
- 「深いモジュール」
- 共通のユースケース向けに単純なインタフェースをもつ
- 隠される=意識させない複雑性を最大化する
英語
- pervasively
- 普及して