A Philosophy of Software Design ch4 Modules Should Be Deep

APoSDソフトウェア設計勉強メモ

出典: 


Modules Should Be Deep

  • modular design

    • 開発者が一度に直面する複雑性を少なくする

      • ソフトウェアの複雑性を御するうえで最重要なテクニック
  • 本章はこの基本原則について述べる

Modular design

  • ソフトウェアシステムはモジュールのコレクションに分解される
  • モジュールとは

    • 比較的独立した部品
    • class, subsystem, service等
  • 理想オブ理想: どのモジュールも、互いに完全に独立している

    • 開発者は、あるモジュールに変更を加える際、他のモジュールのことを一切気にしなくてよい
  • 現実はそうではない

    • モジュール間には依存がある
    • あるモジュールに変更を加えると、他のモジュールも追従して変更が必要になる
    • 例: 関数のシグネチャが変更されたら、呼び出しにも変更が必要
    • 依存にはいろいろな形がある
  • 依存を管理するうえで、モジュールを2つに分けて考える

    • インタフェース

      • あるモジュールを他のモジュールから利用するうえで、知らなければならない情報
      • what
    • 実装

      • インタフェースに準拠して実行されるコード
      • how
  • 例: 平衡木データ構造

    • 利用者に見えるのはinsert, remove, fetch等のみ
    • 木の平衡を保つためになんやかんやしているはずだが、利用者には見えない
  • 本書では、インタフェースと実装をもつ任意のコード単位はすべて「モジュール」ということにする

    • OOPのclass
    • クラスの中のメソッド
    • 関数
    • 高水準のサービス、サブモジュール

        • カーネル呼び出し
        • HTTPリクエスト
  • 重要なのは、インタフェースが実装よりも単純であること

    • モジュールが他に課す複雑性を最小化する
    • モジュールに変更を加えても、インタフェースを変えない限り、他に影響を与えない

      • インタフェースが単純なほうがチャンス多し

What’s in an interface

  • 「インタフェース」は、有形/無形の情報を含む

    • 【補】OOP言語機構としてのinterfaceだけのことを言ってるのではない、ということ
  • 有形

    • コードとして明示されているもの
      • メソッドのシグネチャ
      • 【補】OOP言語機構としてのinterface
    • プログラミング言語により、正しさが保証される

      • 実引数の型と順番と数が仮引数と一致している、等
  • 無形

    • コードとして明示されないもの
      • 「この関数よりも先にあの関数を呼ぶ必要がある」
      • 【補】malloc/freeとか
    • 一般に「あるモジュールを利用するために必要な情報」すべて
    • プログラミング言語によって完全性/正しさを保証できない

      • コメントで表現するほかない
  • ほとんどの場合、無形の情報は有形の情報よりも大きく複雑である
  • インタフェースを明記する利点

    • モジュールを利用するために必要な情報を正確に表す

      • 「分からないことが分からない」がなくなる

        • 【補】複雑性のうち最も深刻なもの

Abstractions

  • ある実体から、重要でない詳細を省いて単純化したビュー

    • 【補】「モデリング」もそうね
  • modular programmingと密接に関わる

    • インタフェースは、モジュールの機能の抽象化

      • 重要でない実装の詳細が省かれる
  • 「重要でない」詳細をそぎ落とせば削ぎ落とすほどよい
  • 抽象化がうまく行かない2つのケース

    • 重要でない詳細を残してしまう

      • 「認知の負荷」になる
    • 重要な情報を削ぎ落としてしまう

      • 「分からないことが分からない」になる
      • false abstraction
  • 抽象化の例: ファイルシステム

  • 抽象化はなにもプログラミングに限った話ではない

    • 例: 電子レンジ

      • 利用者が意識するのは出力と時間のみ
      • 電気をマイクロ波に変えてなんやらかんやら…というのを利用者は意識しない
    • 他にもいろいろ

Deep modules

20191006232742

  • モジュールのべき論
  • モジュールの機能 = インタフェースの複雑性 x モジュールの深さ

    • cf. 長方形の面積 = 幅 x 高さ
    • モジュールの深さとはすなわち、実装の複雑性のうち、抽象化により隠蔽される部分
  • 「深いモジュール」とは、インタフェースが単純で、実装の複雑性の多くの部分が隠蔽されているモジュール
  • 好例: UnixのファイルI/O

    • システムコールはたかだか5種類しかない

      • open
      • read
      • write
      • lseek
      • close
    • 何十万行にもわたる実装が隠蔽されている
    • 実装は何年にも渡り大きく変わってきたが、その間インタフェースは変わっていない

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

      • いらんものに依存しない
  • モジュール利用側は、インタフェースにより提供される「抽象」にのみ依存する

    • 実装の複雑性を意識しない
  • 「深いモジュール」

    • 共通のユースケース向けに単純なインタフェースをもつ
    • 隠される=意識させない複雑性を最大化する

英語

  • pervasively

    • 普及して