Information Hiding (and Leakage)
- ch4.で述べたこと: モジュールは深くあるべき
- 本章: どう作るの
Information hiding
-
知識の隠蔽
- 深いモジュールを作る上で最重要テクニック
- in 1972 David Parnas
-
実装のhowが隠蔽される
-
データ構造やアルゴリズム
- B木にデータを格納する方法、効率的なアクセス方法
- ファイルの論理ブロックとディスクの物理ブロックとの対応づけ
- TCPの実装
- マルチコアプロセッサのスケジューリング
- JSONドキュメントのパース
-
情報の水準はさまざま
-
低水準
- 例: ページサイズ
-
高水準
- 例: 「ほとんどのファイルのサイズが小さい」という仮定
-
-
-
2つの点で複雑性を減らす
-
インタフェースが単純になる
-
重要でない詳細が削ぎ落とされ、開発者の「認知の負荷」が下がる
-
例: B木
- 木の平衡の保ち方などを知る必要がない
-
-
-
システムの拡張が容易になる
- 隠蔽された知識は他のモジュールとの依存関係をもたない
-
ので、独立して変更を加えることができる
-
例: TCP
- 例えば輻輳制御の知識はTCPに閉じている
- L4のTCPの輻輳制御の実装が変更されても、L5以上のプロトコルは影響を受けない
-
-
-
モジュールを設計する際は、なんの知識を閉じ込めるかよく考えよ
- 隠蔽すればするほど、インタフェースは単純になる
- つまり、モジュールは「深く」なる
- 【補】利用者にとって重要な知識は隠蔽してはいけない(ch.6)
-
知識の隠蔽はゼロ百ではない、部分的でも価値がある
-
クラスの知識が全クライアントコードに暴露されているよりは、一部のクラスにのみ公開されているほうがマシ
-
知識を必要とするクラス向けのメソッドを用意する
- 【補】Repository向けのsetterとかはこれ?
-
-
Information leakage
- 隠蔽できてないやつ
-
設計(how)の変更があちこちに跳ねる
-
ある知識が複数のモジュールに反映されている
- 重複
-
ひとかたまりであるべきものが分散している(Back-door leakage)
- 例: データフォーマットのread/writeが別々のモジュールに分かれている
- ひと目でわからないので厄介
- 知識がインタフェースに漏れている
-
-
知識の漏出はソフトウェア設計上、最重要危険信号
- 知識の漏出に敏感であることは最重要スキル
-
知識の漏出に出くわしたら自問自答せよ:
- 「これらのクラスを再構成して、この知識が1つのクラスにしか影響を与えないようにするには?」
- クラス群が小さく、漏出した知識と密に紐付いているなら、1つのクラスにまとめることを検討する
-
あるいは、知識を別クラスに切り出して委譲する
-
切り出した新しいクラスのインタフェースに知識が漏出していたら意味がないことに注意する
- Back-door leakageがインタフェース越しの(普通の)漏出になったたけ
-
Temporal decomposition
- ソフトウェア構造がタスク発生順に対応している
-
時間分割してしまっている例
-
クラス群
- HogeReader
- HogeModifier
- HogeWriter
-
Hogeフォーマットについての知識がReaderとWriterに散らかっている
- Back-door leakage
- まとめよう
-
-
タスクの発生順ではなく、各タスクが必要とする知識に着目する
- 【補】この場合、Hogeフォーマットの読み書き
Example: HTTP server
- HTTPとはなんぞや(略)
- Request/Responseの話
Example: Too many classes
-
やりがちなミス: 時間分割
-
HTTPリクエストを読み、文字列を返すクラス
- 【補】Receiverとする
-
文字列をパースするクラス
- 【補】Parserとする
-
-
パースの知識のBack-door leakage
-
ReceiverとParser両クラスで文字列パースの知識が必要になる
- HTTPリクエストというものは、ある程度パースしないと読めないもの
- 例えば、Content-Lengthヘッダを読まないと、リクエストボディの長さがわからない
-
呼び出しコードに複雑性を強いる
-
Receiver, Parserのメソッドを特定の順番で呼ばせる
- 【補】認知の負荷
-
-
-
知識の隠蔽は、クラスを少し大きくすることで改善することが多い
- 関連機能がまとまる
-
インタフェース単純化
- 【補】クラス減る、メソッド減る
- 深いモジュールにつながる
- かといってアプリケーション全体を1クラスにしていいわけもない
- いつクラスを分割すべきかについてはch9にて論ずる
Example: HTTP parameter handling
-
クエリパラメータをパースする部分は誰もがカプセル化するところ
&
、=
区切りで連想配列にパース+
を半角スペースに
- やりがち: こんな浅いメソッドを生やして、内部表現を暴露してしまう
public Map<String, String> getParams() {
return this.params;
}
- こうあるべき
public String getParameter(String name) {...}
public int getIntParameter(String name) {...}
- 「内部表現が
Map<String, String>
である」という知識を隠蔽した -
getIntParameter
はさらに下記メカニズムを隠蔽- 文字列から整数への変換
- 変換失敗時の例外送出
Example: defaults in HTTP responses
-
やりがちな間違い: デフォルト値を設定すべきものに設定しない
-
例
-
HTTPレスポンスのHTTPバージョン
- リクエストのものと一致するべき
- DATEヘッダ
-
- モジュール利用者が設定するのは、知識の漏出にあたる
-
-
「デフォルト値」は、知識の部分的な隠蔽の例
- 普遍的なケースが可能な限り単純になるように設計する、という原則にしたがっている
- 利用者は、普遍的なケースにおいて、デフォルトが設定された項目の存在について知らない
-
レアケースでは
- デフォルト値をオーバライドする
-
デフォルトのふるまいを変えるための特別なメソッドを起動する
- 【補】UnixのファイルIOの
lseek()
的なやつ
- 【補】UnixのファイルIOの
-
クラスはよしなに「正しいことをする」べき
- デフォルト値はこの一種
-
JavaのファイルI/Oの姿勢はこれに反する
- ファイルI/Oにおけるバッファリングは誰もが欲する機能
- わざわざ指定しなくても有効になってほしい
- 利用者は存在すら知らないのが望ましい
Red Flag: Overexposure
- 普遍的な機能を利用するために、めったに利用されない機能についても学ばなければならない
- 認知の負荷の増大
Information hiding within a class
-
知識の隠蔽は、なにもシステムに対するクラスだけの話ではない
- クラスの中に知識をカプセル化し、システムの残りの部分から隠蔽する
-
クラスに対するメソッド
- privateメソッドの中に知識をカプセル化し、クラスの残りの部分から隠蔽する
-
インスタンス変数の利用箇所を減らせ
- 依存を排除し、複雑性を減らす
Taking it too far
- 隠蔽していいのは、「モジュール外で必要とされない知識」
- モジュール外で必要とされる知識は隠蔽してはいけない
-
例: モジュールのパフォーマンスチューニング用コンフィグ
- ユースケースごとに異なる設定が必要
-
モジュール外で必要な情報は最小化せよ
- 設定値を自動調整できるなら、そのほうが設定項目を公開するよりも良い
- どの知識がモジュール外で必要か認識し、それを確実に公開することが重要
Conclusion
- 知識の隠蔽と深いモジュールとは深く関連している
-
知識を隠蔽するほど…
- モジュールが提供する機能は増える
- インタフェースは小さくなる
- つまり、モジュールは深くなる
- 逆もまた然り
英語
-
pernicious
- 有害な、悪質な