# 情報隠蔽 ## 定義 **情報隠蔽(information hiding)**とは、David Parnas が最初に記述した設計技術であり、各モジュールが少数の知識(設計判断)をカプセル化し、その知識をモジュールの実装内に埋め込みつつ、インターフェースには一切現れないようにすることを指す。隠される情報の典型は、あるメカニズムをどう実装するかに関する詳細(データ構造・アルゴリズム)だが、ページサイズのような低レベルな詳細から、「大半のファイルは小さい」という前提のような高レベルで抽象的な事柄まで幅広く含まれる。 情報隠蔽は[[深いモジュール]]を作るための最も重要な技術である。情報を隠せるほど、モジュールのインターフェースを単純化でき、それがそのままモジュールを深くする。情報隠蔽が複雑性を減らす経路は2つある。第一に、インターフェースをより単純で抽象的にし、モジュール利用者の認知負荷を下げる。第二に、隠された情報に対する外部依存が存在しないため、その情報に関する設計変更の影響を1つのモジュールに閉じ込め、システムの進化を容易にする。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 5 Information Hiding (and Leakage)]] §5.1) なお、クラスの変数・メソッドを `private` 宣言することは情報隠蔽そのものではない。private な要素は外部から直接アクセスできなくする点で情報隠蔽を助けるが、getter/setter のような public メソッドを通じて内部情報がそのまま露出していれば、その変数は public であるのと同程度に露出している。最良の情報隠蔽は情報が完全に隠され利用者にとって無関係・不可視になることだが、一部の利用者だけが使う機能を別メソッド経由でアクセスさせ主要な使用パターンからは見えなくする「部分的な情報隠蔽」にも価値があり、全利用者に見える情報より依存を生みにくい。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 5 Information Hiding (and Leakage)]] §5.1) ## 情報漏出とレッドフラグ **情報漏出(information leakage)**は情報隠蔽の対義語であり、ある設計判断が複数モジュールに反映されてしまうことで、それらのモジュール間に依存が生じる現象を指す。設計判断がモジュールのインターフェースに現れていれば、定義上その情報は漏出している。しかし情報はインターフェースに現れなくても漏出しうる。例えば2つのクラスがそれぞれあるファイル形式を理解している場合(一方が読み、他方が書く)、どちらのインターフェースにもその情報が現れなくても、両クラスはそのファイル形式に暗黙に依存しており、形式が変われば両方の修正が必要になる。このような「裏口漏出(back-door leakage)」は、目に見えるインターフェース経由の漏出より発見しづらく、より悪質だとされる。 > [!warning] レッドフラグ: Information Leakage(情報漏出) > 同じ知識が複数の場所で使われているとき、情報漏出が起きている。例えば、あるファイル形式を2つの異なるクラスがともに理解している場合など。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 5 Information Hiding (and Leakage)]] §5.2) 情報漏出はソフトウェア設計における最重要のレッドフラグの一つに位置づけられ、これに対する高い感度を持つことが優れた設計者の重要なスキルとされる。漏出を見つけたら、(1) 関係するクラスが小さく漏出情報と密接なら1つに統合する、(2) 漏出している知識だけを抜き出して単純なインターフェースを持つ新しいクラスに閉じ込める、のいずれかを検討する。ただし (2) は、新しいクラスがその知識の大半をインターフェースで露出してしまうなら効果がない(裏口漏出をインターフェース経由の漏出に置き換えただけになる)。 ## 時間的分解というレッドフラグ 情報漏出のよくある原因が**時間的分解(temporal decomposition)**である。時間的分解とは、システムの構造を処理が起こる時間順に対応させる設計スタイルを指す。例えば、あるファイル形式のファイルを読み込み・変更し・書き出すアプリケーションを、読み込みクラス・変更クラス・書き出しクラスの3つに分けると、読み込みと書き出しの両方がそのファイル形式を理解する必要があり、情報漏出が生じる。処理の順序は開発時に強く意識されるため陥りやすい罠だが、多くの設計判断はアプリケーションの生存期間を通じて複数の時点で現れるため、時間順の分割は情報漏出を招きやすい。 > [!warning] レッドフラグ: Temporal Decomposition(時間的分解) > 時間的分解では、実行順序がコード構造にそのまま反映され、異なる時点で起こる処理が異なるメソッド・クラスに置かれる。同じ知識が実行の複数時点で使われる場合、その知識が複数箇所に重複して符号化され、情報漏出につながる。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 5 Information Hiding (and Leakage)]] §5.3) 処理順序自体はアプリケーションのどこかに反映されるべきものだが、それがモジュール構造そのものに反映されてよいのは、その構造が情報隠蔽と整合する場合(各段階が互いに全く異なる情報を扱う場合)に限られる。モジュール設計では「いつ実行されるか」ではなく「何の知識が必要か」に注目すべきである。 ## 情報隠蔽をやりすぎた場合の失敗と過剰露出 情報隠蔽が意味を持つのは、隠す情報がモジュール外部で本当に不要な場合に限られる。モジュールの利用ごとに異なる設定が必要な性能パラメータのように、外部で本当に必要な情報を隠してしまうと、それは失敗になる。そうした情報はインターフェースに露出させ、外部から調整できるようにしなければならない。設計者の目標は、モジュールが自動的に設定を調整できるならその方が望ましいという意味で外部に必要な情報量そのものを最小化することであり、同時にどの情報が外部で必要かを正しく認識し、それを確実に露出させることでもある。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 5 Information Hiding (and Leakage)]] §5.9) 情報隠蔽と対称的なもう1つの失敗が**過剰露出(overexposure)**である。よく使われる機能の API が、めったに使わない別の機能まで利用者に学ばせることを強いる場合、それを必要としない利用者の認知負荷を不必要に増やす。デフォルト値を適切に用意しないインターフェースは典型的な過剰露出であり、共通ケースをできるだけ単純にするという原則、および部分的な情報隠蔽の一種(通常時は呼び出し側がデフォルト項目の存在を意識しなくてよい)に反する。 > [!warning] レッドフラグ: Overexposure(過剰露出) > よく使われる機能の API が、めったに使われない別の機能まで利用者に学ばせることを強いている場合、それを必要としない利用者の認知負荷を不必要に増やしている。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 5 Information Hiding (and Leakage)]] §5.7) ## クラス内での部分的な情報隠蔽 情報隠蔽はクラスの外部 API だけでなく、クラス内部にも適用できる。private メソッドは各々が特定の情報や能力をカプセル化し、クラスの他の部分から隠すように設計すべきである。また、各インスタンス変数が使われる箇所の数はできるだけ少なくすべきである。クラス全体で広く使われる変数もあれば、ごく一部の箇所でしか必要とされない変数もあり、後者について使用箇所を減らせれば、クラス内部の依存を減らし複雑性を下げられる。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 5 Information Hiding (and Leakage)]] §5.8) ## 具体例: HTTP サーバ実装に見る成功と失敗 本書第5章は、学生による HTTP サーバ実装(HTTP リクエストの受信・解析とレスポンス生成を行うクラス群)を題材に、情報隠蔽の適用を具体的に検討する。 **隠すべきでなかった情報を隠さなかった(成功)例**: パラメータがリクエストの先頭行にあるかボディにあるかという区別と、パラメータ値の URL エンコーディングの詳細は、サーバアプリケーションにとって本質的に不要な情報である。多くのプロジェクトはこれらを呼び出し側から正しく隠し、両方の場所のパラメータを1つにマージしたうえでデコード済みの値を返すようにしていた。これは情報隠蔽によって呼び出し側の API が単純化された好例である。 **隠すべきだった情報を隠せなかった(失敗)例が3つある**。第一に、リクエストの読み取りクラスと解析クラスを分ける時間的分解であり、Content-Length ヘッダを得るにはヘッダ解析が必要なため両クラスが HTTP リクエスト構造の大半を理解せねばならず、解析コードが重複した(情報漏出)。解決は両者を1つのクラスに統合することであり、これはクラスをわずかに大きくすることでかえって情報隠蔽が改善するという一般的なテーマの例になっている。第二に、`getParams()` が内部の `Map<String, String>` をそのまま返す設計であり、内部表現(データ構造)がそのままインターフェースとして露出してしまっていた。`getParameter(String name)` / `getIntParameter(String name)` のように個々の値を型付きで返すインターフェースにすれば、内部表現を隠しつつ呼び出し側の手間も減らせる。第三に、HTTP レスポンスのプロトコルバージョンや `Date` ヘッダのような妥当なデフォルト値を用意しないインターフェースであり、これは過剰露出の一例である(呼び出し側が本来知る必要のないバージョン情報の指定を強いられる)。 この事例が示すのは、情報隠蔽の成否は「何を外部から見えなくしたか」を個別に検討して初めて評価できるという点であり、抽象的な原則の理解だけでは同じ失敗(時間的分解・シャローなアクセサ・デフォルト値の欠如)を避けられないということである。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 5 Information Hiding (and Leakage)]] §5.4–§5.7) ## 汎用インターフェースと情報隠蔽 第6章は、モジュールを汎用(general-purpose)に設計するほど情報隠蔽が効きやすいことを、学生プロジェクトのGUIテキストエディタ事例で示す。テキストの管理クラスをUI操作に一対一対応する特定用途向けメソッド群(`backspace`/`delete`/`deleteSelection`)で作った場合、選択(selection)やbackspaceキーの挙動といったUI側の設計判断がテキストクラスのインターフェースに反映されてしまい、テキストクラスとUIクラスの間に情報漏出が生じていた。新しいUI機能を追加するたびにテキストクラス側にも新しいメソッドが必要になり、本来独立して開発できるはずの2つのクラスが結合してしまう。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 6 General-Purpose Modules are Deeper]] §6.2) これを`insert`/`delete`という2個の汎用メソッド(`Position` 型の任意区間を対象にする)に置き換えると、テキストクラスはbackspaceキーの扱いのようなUI固有の詳細を一切知る必要がなくなり、情報漏出が解消される。汎用インターフェースはテキストクラスとUIクラスの分離をきれいにし、新しいUI機能を追加してもテキストクラス側に新しいメソッドを増やす必要がなくなる。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 6 General-Purpose Modules are Deeper]] §6.4) 特定用途向けの`backspace`メソッドは「偽の抽象(false abstraction)」の例として提示される。削除される文字の範囲という情報を隠しているように見えるが、UI開発者は実際にはその挙動を正確に知る必要があり、結局メソッドの実装やドキュメントを読みに行く羽目になる。何を隠すべきかを誤ると、情報隠蔽は情報を隠すふりをするだけの不明瞭さ(obscurity)に転じる。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 6 General-Purpose Modules are Deeper]] §6.4) ## パススルー変数と情報隠蔽 第7章§7.5は、情報隠蔽の失敗が単一のクラスやモジュール境界だけでなく、メソッド呼び出しの連鎖という縦方向の経路にも現れることを**パススルー変数(pass-through variable)**という形で示す。パススルー変数とは、多くのメソッドを素通りするだけで、途中のメソッド自身は使わない変数である。データセンターサービスの例では、証明書情報を扱う `cert` 変数が、実際に使うのはソケットを開く末端のメソッド `m3` だけであるにもかかわらず、`main` から `m3` までの経路上のすべての中間メソッドのシグネチャに現れる。これは、その変数を使わない中間層にまで「この変数が存在する」という知識を強制する点で、情報隠蔽の失敗のひとつの現れだと位置づけられる。中間メソッドは本来知る必要のない知識(証明書情報が下流で必要になるという事実)を、シグネチャという形で強制的に抱え込まされる。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 7 Different Layer, Different Abstraction]] §7.5) パススルー変数の解消は難しく、著者は3つの手段とそれぞれの欠点を評価している。第一の手段は、最上位メソッドと最下位メソッドの間ですでに共有されているオブジェクトがあれば、そこに変数を格納する方法である。ただし著者は「その共有オブジェクト自体が、そもそも `m3` がどうやってそれにアクセスするのかという点で、新たなパススルー変数になりうる」という弱点をすぐに指摘する。第二の手段はグローバル変数への格納で、メソッド間の受け渡しそのものは不要になるが、著者は「グローバル変数はほぼ常に別の問題を生む」と評価する。具体的には、同一プロセス内に独立した複数のシステムインスタンスを作れなくなる(本番では不要に見えても、テストでは複数インスタンスがしばしば有用になる)という欠点である。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 7 Different Layer, Different Abstraction]] §7.5) 著者が最も頻繁に使うと述べる第三の手段が**コンテキストオブジェクト(context object)**である。パススルー変数やグローバル変数になりうるアプリケーションのグローバル状態(設定オプション・共有サブシステム・性能カウンタ等)をひとつのオブジェクトにまとめ、システムのインスタンスごとに1個持つ。これにより同一プロセス内で複数インスタンスが共存できる。コンテキストへの参照を主要オブジェクトのインスタンス変数として保存しておけば、新しいオブジェクトを作るメソッドが自身のオブジェクトからコンテキスト参照を取り出しコンストラクタへ渡せるため、コンテキストはほぼどこからでも使えるが、明示的な引数として現れるのはコンストラクタだけになる。新しい変数が必要になっても、影響が及ぶのはコンテキストのコンストラクタ・デストラクタだけで済み、既存コードへの影響を抑えられる。しかし著者はここでも留保を明確にしている。「コンテキストは理想的な解決策には程遠い(far from an ideal solution)」と明言し、コンテキストに格納された変数はグローバル変数の欠点の大半を引き継ぐこと、規律なく使えばコンテキストがシステム全体に不明瞭な依存を作る巨大なグラブバッグと化しうること、スレッド安全性の問題を生みうる(そのため変数は可能な限り不変にすべき)ことを認めている。それでもなお著者は「コンテキストより良い解決策は見つけていない」と結論づけ、消極的な次善策として推奨する。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 7 Different Layer, Different Abstraction]] §7.5) ## 横断的知見 - 第5章は情報漏出の原因として時間的分解(processing順序に沿った分割)を挙げるのに対し、第6章は特定用途向けインターフェース設計そのものが情報漏出の別の原因になることを示す。両章に共通するのは、モジュール境界を「処理の都合」や「呼び出し側の個々の操作」に合わせて引くと、本来閉じ込めたい設計判断がインターフェースを越えて漏れ出すという構図であり、境界を汎用な概念(第6章のテキスト位置操作)や知識のまとまり(第5章)に合わせて引き直すことで解消される点で一致する。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 5 Information Hiding (and Leakage)]], [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 6 General-Purpose Modules are Deeper]]) - 第5章・第6章が扱う情報漏出は「複数のモジュール(クラス)が同じ知識を持つ」という横方向の重複であるのに対し、第7章のパススルー変数は「1つの知識(変数)が、それを必要としない多数の中間層に強制的に運ばれる」という縦方向(呼び出し連鎖)の問題であり、情報隠蔽が失敗する軸がもう1つ存在することを示す。両者とも解消手段は「知識の置き場所を境界の外に出すか、境界そのものを引き直す」という点で共通するが、パススルー変数の解消策(共有オブジェクト・グローバル変数・コンテキストオブジェクト)はどれも著者自身が明確な欠点を認めており、第5・6章の解消策(クラス統合・汎用インターフェース化)ほどきれいには解決しない。情報隠蔽の理想(知識を1箇所に完全に閉じ込める)は、呼び出し連鎖が深くなるほど代償を伴う妥協でしか達成できないことを示唆している。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 5 Information Hiding (and Leakage)]], [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 7 Different Layer, Different Abstraction]]) ## 未解決の問い - 情報隠蔽は[[深いモジュール]]を生む主要な手段だと位置づけられているが、深さを構成する「隠す情報の量」と「インターフェースの単純さ」の関係を定量的に測る基準は本章では示されていない。他章(特に第20章の性能設計)でこの定量化が扱われるか要確認。 - 「隠すべき情報かどうか」の判断基準(§5.9)は事例に依存する形でしか示されておらず、第6章は汎用インターフェースが情報漏出を防ぐ具体例を与えたが、層ごとに異なる抽象(第7章)の議論がこの判断基準をさらにどう具体化するかは未確認。 - 時間的分解のレッドフラグは「処理順序をモジュール構造に反映してよい例外」(各段階が全く異なる情報を扱う場合)に触れているが、その例外に該当するかどうかを見分ける具体的な指針は本章内で示されていない。 - コンテキストオブジェクトは著者自身が「理想的ではない」と認める次善策であり、規律を保つ具体的な方法(何をコンテキストに入れてよいか、いつ別の情報隠蔽手段に切り替えるべきか)は第7章では示されていない。後続章でコンテキストオブジェクトの運用規律に触れる記述があるか要確認。 ## 関連 - ソース: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 5 Information Hiding (and Leakage)]] / [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 6 General-Purpose Modules are Deeper]] / [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 7 Different Layer, Different Abstraction]] - 概念: [[深いモジュール]] / [[抽象化(ソフトウェア設計)]] - 実体: [[John Ousterhout]] ## 出典 - John Ousterhout, *A Philosophy of Software Design*, Yaknyam Press, 2018, Chapter 5. - John Ousterhout, *A Philosophy of Software Design*, Yaknyam Press, 2018, Chapter 6. - John Ousterhout, *A Philosophy of Software Design*, Yaknyam Press, 2018, Chapter 7.