情報隠蔽の規則を十分に適用するためには、非公開と公開という2つのエクスポートでは不十分。 選択的エクスポートが必要。
-- 選択的エクスポートの例 (LINKABLEとLINKED_LIST)
class
LINKABLE[G]
feature [LINKED_LIST] -- 選択的エクスポート
item: G
right: LINKABLE[G]
...
end -- クラスLINKABLE
item
やright
等は、一般の顧客に公開する必要はない- そもそも大抵の顧客は
LINKABLE
を直接操作する必要はないし、するべきでもない
- そもそも大抵の顧客は
- ただし
LINKED_LIST
からは参照可能になっている必要がある- 選択的エクスポートで解決
- 不変表明句で特性を参照したい場合は、自分自身を対象として選択的エクスポートする
メモ: 前の方の章で説明したが、選択的エクスポートはオブジェクト指向ソフトウェア構築における非集中的なアーキテクチャのための必須条件である。 (7.10.6節「アーキテクチャに対する選択的エクスポートの役割」)
感想:
LINKABLE
の例では、別に公開にしておいても良いような気がする- 一般の顧客はそもそもこのクラスを直接参照しない (一般顧客用の特性と特定顧客向けの特性が混在していない)
LINKABLE
が公開する特性としてitem
やright
(or その他)は、自然なものに思える- あまり利用対象を限定しすぎると、依存がきつくなり、使い勝手が悪くなる可能性も
LINKED_LIST
以外のデータ構造実装クラスが、LINKABLE
を利用したい場合は?- メンテナンスが面倒かも?
- ユニットテスト時は?
- C++のfriend指定は結局上手く活用できた覚えがない...
- たまに使いたくなることはある (and コンセプトとしては好き)
- ex. Erlangのbehaviourコールバック関数
- 複数の種類の顧客向けへの特性が(仕方なく)混在している場合に、使いたくなるのかも
- クラス全体が公開(一般顧客向け)か非公開(ライブラリ内部向け)かどうかの区分はほしい
インタフェース設計に関する次の話題はすべてのソフトウェア開発に影響する問題である。 すなわち、通常の望ましい状況から逸脱したケースをどのように処理するかという問題である。
逸脱ケースの例:
- システムのユーザが犯したエラーによるもの
- 操作環境における異常な条件によるもの
- 誤った入力データによるもの
- ハードウェアの故障によるもの
- オペレーティングシステムのバグ
- ほかのモジュールの間違った振る舞いによるもの
これら全てについて考慮しなければならないことは、開発者の悩みの種で、ソフトウェアの複雑さと絶え間ない戦いにおける重大な妨げとなっている。
モジュールインタフェースの設計にも大きく影響する。
例外機構が使えないか?
- 通常ケースについて明確で洗練されたアルゴリズムを書く
- それ以外のすべての処理は外部メカニズムに任せる
- Adaでの例
- 結局、制御構造の一つとして例外が使われてだけ
- その例外をどのように処理するかは、開発者が決める必要がある
- 特殊なケースの全てを例外に任せれば良いというわけではない
まず例外以外の設計技術(「ローテク」だが、びっくりするほど協力)を検討する。
その上で、それでもなお例外が欠かせないケースについても見直す。
事前条件(+ 不変表明)の話。
モジュールインタフェースレベルでの異常なケースを処理する最も重要な基準はおそらく仕様だろう。 個々のソフトウェア要素がどのような入力を受け付ける準備があり、返すときには何を保証するかを厳密に知っていれば、戦いは半ば勝ったようなものである。
- 契約による設計
- 顧客あるいは供給者のいずれか1つだけにあらゆる一貫性の制約に関する責任を取らせることによって信頼性を得る
- 冗長なチェックは極力抑える
- 11.6.2節「ソフトウェアの信頼性の禅とアート:検査が少ないほど保証は大きい」
- 顧客あるいは供給者のいずれか1つだけにあらゆる一貫性の制約に関する責任を取らせることによって信頼性を得る
- 逸脱ケースへの対処方法
- 事前条件に、そのルーチンの操作を可能にするために何が必要かを書く
- その事前条件を満たすのは顧客の責任
-- 顧客による事前条件を満たした呼び出しの例
-- 供給者側
operation(x:...) is
require
precondition (x) -- 事前条件: 顧客はこれを参考にするので、条件は完璧である必要がある
do
...事前条件が満たされている場合にのみ働くコード
end
-- 顧客側(1): if分岐を使った方法
if precondition (y) then
operation (y)
else
...適当な代わりの動作...
end
-- 顧客側(2): 呼び出しに至るコンテクストの中で事前条件を保証する場合
...そのほかの効果とともに precondition (y) を保証する何らかの命令群...
check precondition (y) end -- check命令を使うことが推奨される (意図が明確 and デバッグ可能)
operation (y)
事前条件の演繹的な使い方(と呼べるかもしれない):
- 顧客があらかじめ事前条件を満たすことを保証しなければならない
- エラーが起きないように前もって対策を講じることが顧客に求められている
演繹的な方法では、実行時にエラーがあれば、それはすべて、ある設計の間違い --- すなわち、規則を守らない顧客 --- を表している:
- その場合、唯一の長期的な解決策はエラーを直すこと
- 重大な任務を与えられているシステムについては、表明の違反があったときに
retry
によって部分的な回復を試みるソフトウェア的なフォルトトレラント解を作ることも可能
単純であり、また明確であるがゆえに、演繹的な方法は基本的に理想的な方法。
ただし、広範囲での利用を妨げる理由が3つある:
- A1: 効率性に配慮すると、場合によっては呼び出しの前に事前条件をテストすることが実際的でない場合がある
- 事前条件のテストコストが、関数本体の実行コストと同程度の場合
- ex: 本書の一次方程式の例、JSON文字列のパース関数とかも
- A2: 実際に使われている表明言語の限界から、問題となる表明のいくつかは形式的に表現できない可能性がある
- 例えば「グラフには循環構造が含まれない」や「リストはソートされている」といった要件を表現する能力はない
- ファンクションを使う表明を使うことはできるが、そうすると
A1
のケースに戻ることになる
- A3: 最後に、ルーチンの実行の成功に必要な条件の中には外部のイベントに依存するものもあり、それらは全く表明ではない
- 外界(人間のユーザ、通信回線、ファイルシステム)との対話が含まれてるために、実行を試みずに操作の適用性をテストすることは不可能
- I/O周りは本質的に非決定性を孕んでいる
演繹的な方法がうまく行かないときは、単純な帰納的方法がうまく行く場合がある。 基本的な考え方はまずその操作を試しに実行し、それから、どうだったかを確認するというものである。 実行が失敗したときに回復不能な結果に陥る場合に、この方法はうまく行く。
-- 例: 行列の等式の問題を帰納的に対処したコード
a.invert (b)
if a.inverted then
x := a.inverse
else
...適当なエラー動作 or エラーに対する動作 error action
end
この方法では、エラー条件を発生させるファンクションはすべてプロシージャに変えられる:
- 結果があれば、このプロシージャによって設定された属性を通じてアクセスすることができる
- 毎回必要とされる答えが多くとも1つしかないとき、記憶領域を節約したいのなら、属性ではなくonceファンクションを使っても良い
この方法は入力操作についても上手くいく:
- 失敗する可能性のある「読み出し」ファンクションを以下の二つに分離する
- 読み出しを試すプロシージャ
- 2つの属性
- 操作が成功したかどうかを示す論理属性
- 成功している場合は呼び出した値を表す属性
- コマンドクエリ分離原則にも沿っている
この例から失敗への対処に関する不変の原則の1つが明らかになる。 すなわち、それが可能ならば、失敗から回復するための方法よりも失敗を回避する方法の方が望ましい。
! 演繹的な方法の方が望ましい、ということ?
これまでで、ほとんどの異常ケースでは、演繹的な方法と帰納的な方法(+ 標準的な制御構造)の組み合わせで十分に対処可能であることが分かった。
ただし、これまでの説明では次のような3つのカテゴリに含まれるケースが残されたままになっている:
- ソフトウェアがその例外を認識し、実行を即座に止めない限り、ハードウェアやOSが動作を先取りすることがある
- 数値計算例外やメモリ不足などの異常なイベントが発生した場合
- 継続的に利用できることが求められるシステム(ex. 電話交換システムや医療システム)においては、このような状況は耐え難い場合は多い
- 事前条件による検出ができないような、異常な状況のいくつかは可能な限り早い時期に診断しなければならない
- 帰納的なチェックをするために操作を最後まで実行することは許されないケース
- データベースの統合性が失われる
- ロボット制御システムなどでは、人命を危険にさらすような悲劇的な結果を招く可能性がある
- 帰納的なチェックをするために操作を最後まで実行することは許されないケース
- 開発者が残されているあらゆるエラーによって引き起こされる最も悲劇的な状況に対する何らかの形式の保護策をソフトウェアの中に取り込みたいと考える場合
- ソフトウェアのフォルトトレランスのために例外を利用する例
このようなケースについては、例外に基づいた方法が必要なように見える。 前の方の章で示したきちんとした例外機構ならば適切なツールが用意されているはずである。
作成済みのクラスのインタフェースの一部分を設計し直し、改善した方が良いと気付いたとしたらどうだろう?
二者択一を迫られ、あまり嬉しくない状況になる:
- 現在のユーザを優先させる:
- もはや古くなった設計のまま使い続け、不愉快な使い心地は時が経つにつれますますひどくなる
- コンピュータ業界では資産互換性(upward compatibility)と呼ばれている
- 未来のユーザを優先させる:
- 現在のユーザを困らせることになる
- 彼等の唯一の罪は、あなたを信用する時期が早すぎたということである
最終手段として obsolete特性 あるいは obsoleteクラス が使える:
enter
からput
に特性名を変更した例- obsolete宣言をつけておくと、使用箇所でコンパイラが警告を出力してくれる
- obsolete宣言をつけた特性(or クラス)は、次の大きなリリースのタイミングで削除すべき
- obsolete宣言 + 十分な移行期間、を用意すれば、ユーザにあまりダメージを与えることなく移行が行える
特性とクラスのobsolete化は、ある特定の問題を解決するに過ぎない。 開放/閉鎖の原則の説明と、継承を使った元の設計を妨げずに親の設計を適合する方法の説明はこの場合にも完全に当てはまる。 すなわち、設計に 欠陥 があるとき、唯一の合理的なアプローチは、現在のユーザがうまく以降できるように最善の努力を払いながら、その欠陥を直すことである。
再定義付きの継承もobsoleteも既存のソフトウェアのバグをもみ消すために使ってはならない。 しかし、obsoleteは、もとの設計が、ほかの面では満足できるが、現在の見方に合わない場合に貴重である。 この典型的な原因は、前は今よりも視界が狭く、明確ではなかったことである。 古い設計でも根本的に悪いところはなかったのだが、より単純なインタフェース、ほかの部分とのより良い一貫性、ほかのプロダクトとの相互操作性、より良い命名規則など、改善の余地はある。 そのような場合には、いくつかの特性とクラスをobsoleteにすることは、より明るい未来へ進みながら、現在のユーザの投資を守る優れた方法である。
- 良いクラスインタフェース設計と同様に、良いインタフェースドキュメントも重要
- クラスレベルだけでなくシステムレベルのドキュメントも生成可能
- 以降でショート形式について述べていることはフラット-ショート形式にも当てはまる
ショート形式は顧客の目からすべての非公開の情報を見えなくすると言う方法で情報隠蔽の原則を直接的に適用する
非公開の情報:
- エクスポートされていないすべての特性とその特性に関わるすべてのもの
do...
句として記述されるルーチンの実現のすべて
ショート形式では、実装に依存しない、顧客が必要とする抽象的な情報のみが公開される。
ただし、ショート形式の目的は抽象(援助)であって、保護(妨害)ではない。(ex. ソースコードには参照可能)
Javaのようなモジュールインタフェースを分離する方法は、著者は非推奨。
(進化を妨げる。ソースコードは同じにして、ツールで分離すべき)
ソフトウェアそのものができる限りドキュメントになるようにすることが大事。
ドキュメント作成の原則
--------------------
さまざまな抽象化のレベルで自動的にドキュメント要素を抽出するツールが認識できるように、
ドキュメントに必要な要素がすべて入ったソフトウェアを書くように心がけること。
この原則は、より一般的な自己文書化の原則を開発者が日々実践できる実用的な規則に書き換えたもので、特に重要なのは次の事柄:
- 良く設計された事前条件、事後条件、および、不変表明
- クラスと特性の両方の名前の選択に注意すること
- 情報を与える indexing 句
Caseツールの紹介
- モジュールレベルだけでなく、システムレベルのドキュメントも生成可能
- その場合は、テキスト形式だけでは不十分で、図形による記述もほしい
- ISE環境のCaseツールは図形の出力もサポート
- 他にも大規模なシステムの探索をサポートする機能が盛り沢山
これらのツールはすべてドキュメント作成の原則の応用であり、注意深く設計された表記法とより進歩した環境のおかげで、ドキュメントの自動生成という理想にますますわれわれを近づける、そんなソフトウェアの生産に貢献している。
本を参照
省略
省略