A Philosophy of Software Design ch6. General-Purpose Modules Are Deeper

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

出典: 


General-Purpose Modules Are Deeper

  • モジュールを汎用にするか用途特化にするか
  • 汎用サイドの言い分

    • 広範な問題に対応できる

      • まだ見ぬ将来の問題にも
    • 将来の工数短縮
    • ch3. Working Code Isn’t Enough にも沿っているように見える
  • 用途特化サイドの言い分

    • 機能を汎用的に作ったはいいが、一度も使われないかも
    • 汎用的すぎると、今現在挙がっている問題にうまく対処できない

      • 【補】使い方が面倒だったりする?(認知の負荷)
    • 都度リファクタリングして汎用にしていく
    • インクリメンタルアプローチに沿っているように見える

Make classes somewhat general-purpose

  • 著者の経験上、「やや汎用」がスイートスポット

    • 実装には今現在のニーズを反映する
    • インタフェースは汎用にする

      • 複数用途に利用できる程度には汎用
      • それでいて今現在のニーズにも簡単に利用できる
  • 汎用の利点

    • 他用途に流用できれば、将来の工数短縮
    • 他用途に流用できても、単純になる(意外な利点)

      • インタフェースが単純で、実装をよく隠蔽した「深い」モジュールになる

Example: storing text for an editor

  • 用途特化モジュールがダメな例
  • これは良くない:
class Text...
    void backspace(Cursor cursor);
    void delete(Cursor cursor);
  • UI側で範囲削除が欲しくなったらTextクラスに↓を生やしますか?
class Text...
    void deleteSelection(Selection selection)
  • UIに関する知識の漏出

    • 「選択」や「バックスペース」がTextに反映されてしまっている
    • ので、UIを開発する者はTextクラスも開発しなければならなくなる
    • モジュール設計の目的「個々のモジュールを独立して開発できる」に反する

A more general-purpose API

  • 汎用インタフェースの例
class Text...
    void insert(Position position, String newText);
    void delete(Position start, Position end);
  • UI側で1文字削除が欲しくなったら:
text.delete(cursor, text.changePosition(cursor, +1));
  • UI側でバックスペースが欲しくなったら:
text.delete(text.changePosition(cursor, -1), cursor);
  • 用途特化モジュールとの比較

    • 呼び出しのコードはいくぶん長くなるが…
    • 明瞭である

      • UI開発者から見て、どの文字が消えるのか一目瞭然
    • コード量減る

      • Textクラスに生えている大量の用途特化メソッドを消すことができる
    • UIの知識が漏出していない

      • インタラクティブエディタ以外からも利用できる

Generaity leads to better information hiding

  • 知識の隠蔽

    • UIについてTextクラスは知る必要がない
  • 認知の負荷が低い

    • 開発者が知る必要があるのは、少数の単純なメソッドのみ

      • 様々な目的に流用できる
  • void backspace()はfalse abstraction

    • 抽象化とは「重要でない詳細を削ぎ落とす」こと
    • 利用側にとって重要な詳細は抽象化・隠蔽してはいけない

      • 「どの文字が消えるか」という知識が隠蔽されている
      • が、UIは「どの文字が消えるか」知らなければならない
      • 「不明瞭」となり、複雑性をもたらす

        • 【補】認知の負荷、もしくは「わからないことがわからない」につながる
    • ソフトウェア設計上、誰が・何を・いつ知る必要があるかは最重要

Questions to ask yourself

  • 行うは難し

    • 汎用クラスを実際に作るのは難しい
  • 汎用と用途特化との間のよい落とし所を探るために、次のことを自問自答せよ:

What is the simplest interface that will cover all my current needs?

  • 機能を保ったままメソッドの数を減らすことができれば、汎用のメソッドを作ったことになる
  • メソッドの数を減らすべきなのは、個々のメソッドを単純なままに保てる場合のみ
  • 「メソッドを減らすために大量の引数を追加する」というのは、単純化になっていない可能性がある

In how many situations will this method be used?

  • 特定の1用途向けのメソッドは危険信号

    • Textクラスのbackspace()メソッドみたいなやつ
  • 汎用メソッドにまとめられないか検討する

Is this api easy to use for my current needs?

class Text...
    void insertChar(Position position, Char newChar);
    void deleteChar(Position position);
  • 汎用メソッドの別解
  • 「文字列を挿入したい」「範囲削除したい」というニーズには使いづらい

    • 利用側にループを強いる
    • 効率も悪い

Conclusion

  • 汎用インタフェースは用途特化インタフェースより優れた点多数

    • 「深いモジュール」になる傾向

      • 少数の、深いメソッド
    • クラス間をきれいに分離

      • cf. 用途特化は知識の漏出をまねきがち
  • 「やや汎用」のモジュールを作ることが、システム全体の複雑性を減らす最良の方法