Skip to content

Instantly share code, notes, and snippets.

@sile
Last active August 29, 2015 14:12
Show Gist options
  • Save sile/61096bd6061df36b7e9d to your computer and use it in GitHub Desktop.
Save sile/61096bd6061df36b7e9d to your computer and use it in GitHub Desktop.
オブジェクト指向入門 第2版 方法論・実践 - 第23章5節~第24章2節

23.5 選択的エクスポート

情報隠蔽の規則を十分に適用するためには、非公開と公開という2つのエクスポートでは不十分。 選択的エクスポートが必要。

-- 選択的エクスポートの例 (LINKABLEとLINKED_LIST)

class
  LINKABLE[G]
feature [LINKED_LIST] -- 選択的エクスポート
  item: G
  right: LINKABLE[G]
  ...
end -- クラスLINKABLE
  • itemright等は、一般の顧客に公開する必要はない
    • そもそも大抵の顧客はLINKABLEを直接操作する必要はないし、するべきでもない
  • ただしLINKED_LISTからは参照可能になっている必要がある
    • 選択的エクスポートで解決
  • 不変表明句で特性を参照したい場合は、自分自身を対象として選択的エクスポートする

メモ: 前の方の章で説明したが、選択的エクスポートはオブジェクト指向ソフトウェア構築における非集中的なアーキテクチャのための必須条件である。 (7.10.6節「アーキテクチャに対する選択的エクスポートの役割」)

感想:

  • LINKABLEの例では、別に公開にしておいても良いような気がする
    • 一般の顧客はそもそもこのクラスを直接参照しない (一般顧客用の特性と特定顧客向けの特性が混在していない)
    • LINKABLEが公開する特性としてitemright(or その他)は、自然なものに思える
    • あまり利用対象を限定しすぎると、依存がきつくなり、使い勝手が悪くなる可能性も
      • LINKED_LIST以外のデータ構造実装クラスが、LINKABLEを利用したい場合は?
      • メンテナンスが面倒かも?
      • ユニットテスト時は?
    • C++のfriend指定は結局上手く活用できた覚えがない...
  • たまに使いたくなることはある (and コンセプトとしては好き)
    • ex. Erlangのbehaviourコールバック関数
    • 複数の種類の顧客向けへの特性が(仕方なく)混在している場合に、使いたくなるのかも
    • クラス全体が公開(一般顧客向け)か非公開(ライブラリ内部向け)かどうかの区分はほしい

23.6 異常ケースを処理する

インタフェース設計に関する次の話題はすべてのソフトウェア開発に影響する問題である。 すなわち、通常の望ましい状況から逸脱したケースをどのように処理するかという問題である。

逸脱ケースの例:

  • システムのユーザが犯したエラーによるもの
  • 操作環境における異常な条件によるもの
  • 誤った入力データによるもの
  • ハードウェアの故障によるもの
  • オペレーティングシステムのバグ
  • ほかのモジュールの間違った振る舞いによるもの

これら全てについて考慮しなければならないことは、開発者の悩みの種で、ソフトウェアの複雑さと絶え間ない戦いにおける重大な妨げとなっている。
モジュールインタフェースの設計にも大きく影響する。

例外機構が使えないか?

  • 通常ケースについて明確で洗練されたアルゴリズムを書く
  • それ以外のすべての処理は外部メカニズムに任せる
  • Adaでの例
    • 結局、制御構造の一つとして例外が使われてだけ
    • その例外をどのように処理するかは、開発者が決める必要がある
  • 特殊なケースの全てを例外に任せれば良いというわけではない

まず例外以外の設計技術(「ローテク」だが、びっくりするほど協力)を検討する。
その上で、それでもなお例外が欠かせないケースについても見直す。

23.6.1 演繹的方法

事前条件(+ 不変表明)の話。

モジュールインタフェースレベルでの異常なケースを処理する最も重要な基準はおそらく仕様だろう。 個々のソフトウェア要素がどのような入力を受け付ける準備があり、返すときには何を保証するかを厳密に知っていれば、戦いは半ば勝ったようなものである。

  • 契約による設計
    • 顧客あるいは供給者のいずれか1つだけにあらゆる一貫性の制約に関する責任を取らせることによって信頼性を得る
      • 冗長なチェックは極力抑える
    • 11.6.2節「ソフトウェアの信頼性の禅とアート:検査が少ないほど保証は大きい」
  • 逸脱ケースへの対処方法
    • 事前条件に、そのルーチンの操作を可能にするために何が必要かを書く
    • その事前条件を満たすのは顧客の責任
-- 顧客による事前条件を満たした呼び出しの例

-- 供給者側
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によって部分的な回復を試みるソフトウェア的なフォルトトレラント解を作ることも可能

23.6.2 演繹的な方法に対する障害

単純であり、また明確であるがゆえに、演繹的な方法は基本的に理想的な方法。

ただし、広範囲での利用を妨げる理由が3つある:

  • A1: 効率性に配慮すると、場合によっては呼び出しの前に事前条件をテストすることが実際的でない場合がある
    • 事前条件のテストコストが、関数本体の実行コストと同程度の場合
    • ex: 本書の一次方程式の例、JSON文字列のパース関数とかも
  • A2: 実際に使われている表明言語の限界から、問題となる表明のいくつかは形式的に表現できない可能性がある
    • 例えば「グラフには循環構造が含まれない」や「リストはソートされている」といった要件を表現する能力はない
    • ファンクションを使う表明を使うことはできるが、そうするとA1のケースに戻ることになる
  • A3: 最後に、ルーチンの実行の成功に必要な条件の中には外部のイベントに依存するものもあり、それらは全く表明ではない
    • 外界(人間のユーザ、通信回線、ファイルシステム)との対話が含まれてるために、実行を試みずに操作の適用性をテストすることは不可能
    • I/O周りは本質的に非決定性を孕んでいる

23.6.3 帰納的方法

演繹的な方法がうまく行かないときは、単純な帰納的方法がうまく行く場合がある。 基本的な考え方はまずその操作を試しに実行し、それから、どうだったかを確認するというものである。 実行が失敗したときに回復不能な結果に陥る場合に、この方法はうまく行く。

-- 例: 行列の等式の問題を帰納的に対処したコード

a.invert (b)
if a.inverted then
  x := a.inverse
else
  ...適当なエラー動作 or エラーに対する動作 error action
end

この方法では、エラー条件を発生させるファンクションはすべてプロシージャに変えられる:

  • 結果があれば、このプロシージャによって設定された属性を通じてアクセスすることができる
  • 毎回必要とされる答えが多くとも1つしかないとき、記憶領域を節約したいのなら、属性ではなくonceファンクションを使っても良い

この方法は入力操作についても上手くいく:

  • 失敗する可能性のある「読み出し」ファンクションを以下の二つに分離する
    • 読み出しを試すプロシージャ
    • 2つの属性
      • 操作が成功したかどうかを示す論理属性
      • 成功している場合は呼び出した値を表す属性
  • コマンドクエリ分離原則にも沿っている

この例から失敗への対処に関する不変の原則の1つが明らかになる。 すなわち、それが可能ならば、失敗から回復するための方法よりも失敗を回避する方法の方が望ましい。

! 演繹的な方法の方が望ましい、ということ?

23.6.4 例外機構の役割

これまでで、ほとんどの異常ケースでは、演繹的な方法と帰納的な方法(+ 標準的な制御構造)の組み合わせで十分に対処可能であることが分かった。

ただし、これまでの説明では次のような3つのカテゴリに含まれるケースが残されたままになっている:

  • ソフトウェアがその例外を認識し、実行を即座に止めない限り、ハードウェアやOSが動作を先取りすることがある
    • 数値計算例外やメモリ不足などの異常なイベントが発生した場合
    • 継続的に利用できることが求められるシステム(ex. 電話交換システムや医療システム)においては、このような状況は耐え難い場合は多い
  • 事前条件による検出ができないような、異常な状況のいくつかは可能な限り早い時期に診断しなければならない
    • 帰納的なチェックをするために操作を最後まで実行することは許されないケース
      • データベースの統合性が失われる
      • ロボット制御システムなどでは、人命を危険にさらすような悲劇的な結果を招く可能性がある
  • 開発者が残されているあらゆるエラーによって引き起こされる最も悲劇的な状況に対する何らかの形式の保護策をソフトウェアの中に取り込みたいと考える場合
    • ソフトウェアのフォルトトレランスのために例外を利用する例

このようなケースについては、例外に基づいた方法が必要なように見える。 前の方の章で示したきちんとした例外機構ならば適切なツールが用意されているはずである。

23.7 クラスの進化:obsolete句

作成済みのクラスのインタフェースの一部分を設計し直し、改善した方が良いと気付いたとしたらどうだろう?

二者択一を迫られ、あまり嬉しくない状況になる:

  • 現在のユーザを優先させる:
    • もはや古くなった設計のまま使い続け、不愉快な使い心地は時が経つにつれますますひどくなる
    • コンピュータ業界では資産互換性(upward compatibility)と呼ばれている
  • 未来のユーザを優先させる:
    • 現在のユーザを困らせることになる
    • 彼等の唯一の罪は、あなたを信用する時期が早すぎたということである

最終手段として obsolete特性 あるいは obsoleteクラス が使える:

  • enterからputに特性名を変更した例
  • obsolete宣言をつけておくと、使用箇所でコンパイラが警告を出力してくれる
  • obsolete宣言をつけた特性(or クラス)は、次の大きなリリースのタイミングで削除すべき
  • obsolete宣言 + 十分な移行期間、を用意すれば、ユーザにあまりダメージを与えることなく移行が行える

特性とクラスのobsolete化は、ある特定の問題を解決するに過ぎない。 開放/閉鎖の原則の説明と、継承を使った元の設計を妨げずに親の設計を適合する方法の説明はこの場合にも完全に当てはまる。 すなわち、設計に 欠陥 があるとき、唯一の合理的なアプローチは、現在のユーザがうまく以降できるように最善の努力を払いながら、その欠陥を直すことである。

再定義付きの継承もobsoleteも既存のソフトウェアのバグをもみ消すために使ってはならない。 しかし、obsoleteは、もとの設計が、ほかの面では満足できるが、現在の見方に合わない場合に貴重である。 この典型的な原因は、前は今よりも視界が狭く、明確ではなかったことである。 古い設計でも根本的に悪いところはなかったのだが、より単純なインタフェース、ほかの部分とのより良い一貫性、ほかのプロダクトとの相互操作性、より良い命名規則など、改善の余地はある。 そのような場合には、いくつかの特性とクラスをobsoleteにすることは、より明るい未来へ進みながら、現在のユーザの投資を守る優れた方法である。

23.8 クラスドキュメントとシステムドキュメントの作成

  • 良いクラスインタフェース設計と同様に、良いインタフェースドキュメントも重要
  • クラスレベルだけでなくシステムレベルのドキュメントも生成可能
  • 以降でショート形式について述べていることはフラット-ショート形式にも当てはまる

23.8.1 インタフェースを見せる

ショート形式は顧客の目からすべての非公開の情報を見えなくすると言う方法で情報隠蔽の原則を直接的に適用する

非公開の情報:

  • エクスポートされていないすべての特性とその特性に関わるすべてのもの
  • do...句として記述されるルーチンの実現のすべて

ショート形式では、実装に依存しない、顧客が必要とする抽象的な情報のみが公開される。
ただし、ショート形式の目的は抽象(援助)であって、保護(妨害)ではない。(ex. ソースコードには参照可能)

Javaのようなモジュールインタフェースを分離する方法は、著者は非推奨。
(進化を妨げる。ソースコードは同じにして、ツールで分離すべき)

ソフトウェアそのものができる限りドキュメントになるようにすることが大事。

ドキュメント作成の原則
--------------------

さまざまな抽象化のレベルで自動的にドキュメント要素を抽出するツールが認識できるように、
ドキュメントに必要な要素がすべて入ったソフトウェアを書くように心がけること。

この原則は、より一般的な自己文書化の原則を開発者が日々実践できる実用的な規則に書き換えたもので、特に重要なのは次の事柄:

  • 良く設計された事前条件、事後条件、および、不変表明
  • クラスと特性の両方の名前の選択に注意すること
  • 情報を与える indexing

23.8.2 システムレベルのドキュメント作成

Caseツールの紹介

  • モジュールレベルだけでなく、システムレベルのドキュメントも生成可能
    • その場合は、テキスト形式だけでは不十分で、図形による記述もほしい
  • ISE環境のCaseツールは図形の出力もサポート
    • 他にも大規模なシステムの探索をサポートする機能が盛り沢山

これらのツールはすべてドキュメント作成の原則の応用であり、注意深く設計された表記法とより進歩した環境のおかげで、ドキュメントの自動生成という理想にますますわれわれを近づける、そんなソフトウェアの生産に貢献している。

23.9 本章のまとめ

本を参照

23.10 参考文献

省略

23.11 演習問題

省略

第24章 継承の上手な使い方

  • パートCでは継承の技術的な詳細を学んだが、実際にどう使うのが良いのか、というのはまた別の話
  • この章では継承の意味についてさらに深く探求していく
    • 理論のためではなく、継承を確実に最も良い方法で使うことによってソフトウェア開発プロジェクトの中で活かすため
  • 特に(継承のライブルである)顧客関係との使い分けは大事

24.1 継承を使わずに済ませるには?

まずは継承が不適切な例から:

  • CAR + PERSON => CAR_OWNER
  • 「である(is-a関係)」と「がある(has-a関係)」の誤用
    • PERSONはCARを所有しているが、PERSON自身がCARである訳ではない

以下の継承の基本原則を思い出す必要がある:

継承の「・・・は・・・の一種である」(Is-a)の原則
---------------------------------------------

BのすべてのインスタンスはAのインスタンスとしても
見ることができると何らかの方法で主張できない限り、
クラスBはクラスAから継承してはならない。

これは厳密な規則ではなく、緩やかな規則:

  • 「何らかの方法で主張できない限り」というフレーズに注意
    • すべてのBがAであるという 証拠 は必要ではない
    • 議論の余地があるケースは数多くある
      • 「すべての普通預金講座が当座預金口座である」は真実か?
      • ケースバイケース
    • ただし、CAR_OWNERはCARの「一種である」という主張が支持できないことには変わりはない
  • __is-a__の意味するものに対するわれわれの見方は特に自由である
    • 主張が合理的であるなら、多くの人々が疑いのまなざしで見ている継承の一形態である 実装継承 ですら許さないことはない

これらの見解はis-aの原則の有効性と限界の両方を明確にしている:

  • 否定的 な規則としては有効
  • 肯定的な規則としては、十分ではない
    • この規則のテストを通過する使い方すべてが適切と言うわけではない

以降では、継承が適切かどうかが不明瞭なケースに対する、幾つかの一般的な指針を探っていく。

24.2 買うべきか、それとも継承すべきか?

顧客と継承という2つのモジュール間関係から選択するときの基本原則は嘘かと思えるほど単純である。 すなわち、顧客は「がある(has)」であり、継承は「である(is)」である。 それならば、選択することが簡単ではないのはなぜだろうか?

24.2.1 「がある」ということと「である」ということ

その理由は、「がある」は常に「である」とは限らないのに対し、多くの場合、「である」は同時に「がある」でもあるためである。

例:

  • [A] is-a: すべてのソフトウェア技術者は技術者である
  • [B] has-a: すべてのソフトウェア技術者の中には技術者が宿る
  • [C] has-a: すべてのソフトウェア技術者には「技術者」の要素がある

幸運なことに、そのような話し合い((継承と顧客のどちらが適切か?)を助けるものとして2つの基準がある。

当たり前のことだが(一般的な設計についての問題なので)、基準を適用しても1つの明確な解決策が得られないこともときにはあるかもしれない。 しかし、実際の多くのケースでは、2つの関係のうちどちらかが正しいかが即座に分かることだろう。

都合の良いことに、2つの基準のうちの1つが継承を推奨し、もう1つが顧客を推奨している。

24.2.2 変更の法則

顧客関係は通常変更を許すが、継承関係は変更を許さない:

  • 継承を定義づける属性の1つは、継承がオブジェクトではなく クラス間 の関係
    • 関係を変えたければクラス(の定義)を変更するしかなく、実行時に変更することはできない
  • 顧客関係では、制約はもっと緩やかで、(型に即している限りは)実行時に変更が可能

エンジニアの例:

  • 顧客ならVOCATIONのように職業が指定できて柔軟
変更の法則
----------

対応するオブジェクト要素を実行時に変更しなければならない可能性あるなら、
「・・・は・・・の一種である」関係と認められる関係を表すのに継承を使ってはならない。

補足:

本当に興味深いケースは SOFTWARE_ENGINNEER_3 のケースである。 ...略...。VOCATIONは上位レベルのクラスで、おそらく、暫定クラスである。したがって、属性は多くの型のオブジェクトを(多相性によって)表すことができる。

これは、また、主な関係として顧客を使ったとしても、現実には、最終的な形態で補足的に継承を用いる場合も多いことを意味している。

24.2.3 多相性の法則

さて、次は、継承を要求し、顧客を排除する基準である。 この基準は単純で、多相的な使用である。

多相性の法則
-----------

より汎用的な型のエンティティまたはデータ構造要素を
より特殊な型のオブジェクトに関連付ける必要が生じる可能性がある場合、
「・・・は・・・の一種である」と思われる関係を示すのに継承が適している。

24.2.4 まとめ

単なる繰り返し

顧客を選ぶか、継承を選ぶか
------------------------

クラスBがクラスAに依存しているとき、その依存関係の表現方法は次の基準を適用して、決定する。

[CI1]
Bのすべてのインスタンスが最初はA型の要素を持っていて、
実行時にその要素をほかの型のオブジェクトと置き換える必要が
生じる可能性がある場合は、BをAの顧客にする。

[CI2]
A型のエンティティがB型のオブジェクトを保持する必要があるか、
もしくはA型のオブジェクト群(そのうちのいくつかは実際にはB型の可能性がある)を
扱う多相的な構造が必要な場合は、BをAの後継者にする。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment