契約による設計
事前条件違反をどう扱うか
"可能性その1:顧客に責任を割り当てる。この場合、条件はルーチンの事前条件の一部として表される。"
"可能性その2:供給者に責任を割り当てる。この場合、条件はルーチン本体に、if condition then ...の形式の条件文、もしくは同等な制御構造で表される。"
この例は、check命令が最も役に立ちそうな応用として、代表的なものである。すなわち、特定の事前条件を持つ(ここでは、sqrtは、負でない引数を要求するような事前条件を持つと仮定する)ルーチンを呼ぶ前に、そのような命令を加えなさい。ただし、呼び出しは事前条件を満たしているが、コンテクストからはすぐにそれがわからないと確信した場合に使う。
表明違反規則
実行時の表明違反は、そのソフトウェアにバグがある証拠である。
事前条件違反は顧客側にバグがある証拠である。
事後条件違反は供給者側にバグがある証拠である。
table: 命令的-適用的の対照表(『オブジェクト指向入門』455ページ)
実装(Implementation) 仕様(Specification)
命令(instruction) 表現(Expression)
どのように(How) 何を(What)
命令的(Imperative) 適用的(Applicative)
指示的(Prescription) 記述的(Description)
事前条件と事後条件
コンストラクタで事前条件をifを使って検証して例外をスローする 達人プログラマー
Bertrand Meyerは、契約による設計というコンセプトをEiffelという言語で採用しました{一部はDijkstra、Floyd、Hoare、Wirthや他の先駆者達の成果に基づいています。Eiffel自身についての詳細は[URL12]と[URL13]を参照してください。}。これは、ソフトウェアモジュールの権利と責任を文書化(そして承諾)し、プログラムの正しさを保証するための簡潔かつパワフルな技法です。では、正しいプログラムとは一体何でしょうか?これは、要求されたこと以上のことも、それ以下のことも行わないというものです。要求の文書化および検証は、契約による設計(Design by Contract/以下DbCと省略します)の核となるものです。 オブジェクト指向入門
非冗長性の原則
どんな事情があっても、ルーチンの事前条件にあたるテストを、ルーチンの本体で行ってはならない。
この規則は、ソフトウェア工学あるいはプログラミング方法論の多くの教科書で、しばしば「防御的プログラミング(defencsive programming)」という名の下に主張されていることとは逆である。防御的プログラミングとは、信頼性の高いソフトウェアを得るために、システムのすべてのコンポーネントは、できるだけ自己防衛をするように設計されなければならないとする考えである。検査は不十分であるよりは多過ぎるが良いというのが、このアプローチである。冗長性のある検査は、救いにはならないかもしれないが、少なくとも損傷させることはないだろうと。
契約による設計は逆の観点から始まる。つまり、冗長性のある検査は損傷を与える可能性があるだけでなく、実際、損傷を与える。もちろん、一見、これは奇妙に思える。余計な検査(例えば、呼び出すものが x>=0 を保証するように命令されているのに、ルーチン sqrt に、 x<0 にあたる条件命令テストが含まれる)は、最悪、無駄ではあっても、ダメージを与えるわけではないだろうというのが、当たり前の反応である。しかしながら、そのようなコメントは、 sqrt のような個々のソフトウェア要素に焦点を合わせた、信頼性の微視的な理解から生ずるものである。 sqrt の狭い世界に視点を制限するならば、足りないよりは過剰なテストでルーチンがより堅固になるように思える。しかし、1つのシステムの世界が一個のルーチンに制限されるわけではない。たくさんのクラスのたくさんのルーチンを含んでいる。信頼性の高いシステムを得るためには、微視的な観点から、アーキテクチャ全体にわたる巨視的な観点に、視点を転じなければならない。
表明は入力検査メカニズムにあらず
陥りやすい誤解を避けるために、ここで確認しておこう。これまで話題にしてきた契約は、それぞれルーチン間(供給者と顧客)で取り交わすものである。つまり、関心があるのはソフトウェア動詞のコミュニケーションであって、ソフトウェアと人間のコミュニケーションでもないし、ソフトウェアと外界のコミュニケーションでもない。また、事前条件はユーザ入力の面倒を見るわけではない。(例えば、対話形式でユーザが入力を行うとき、正の数を入力することを期待する read_positive_integerルーチンのように)次のような形式の事前条件をルーチン内に含んだからといって、これは単なる希望的観測であって信頼性の技法ではない。ここには、お決まりの条件検査の構成要素(古めかしいif...then...を含む)に代わるものはない。こういった場合、次の章で学ぶ例外処理メカニズムが役に立つだろう。
入力データの検証に関するこの問題を解決するのに、表明は果たすべき役割を持つ。モジュールの保護性の基準線内で、センサ、ユーザ入力、ネットワークなど外界から得られたオブジェクトすべての検証を推奨するやり方がある。その場合、オブジェクトのソースのできるだけ近くで行う【近くなければ、実際にルーチンが呼び出されるまでの間に状態が変化している可能性があるからだろう。引用メモ】。また、必要であれば「フィルタ(filter)」モジュールを使う。
Eiffel
Java等のassertに対応する表明命令はcheck命令
code:STACK1.e
feature -- 要素を変更する
remove is
-- 最上部の要素を排除する
require -- *事前条件*
not empty
do -- *処理本体*
...
ensure -- *事後条件*
not full
count = old count - 1 -- *"old" はルーチン実行前の値*
end
end
Eiffel provides syntax for expressing preconditions (require), postconditions (ensure) and class invariants (invariant), as well as other assertion constructs studied later (see "Instructions" ): loop invariants and variants, check instructions.
hr.icon
個人のScrapboxに書き殴ったものを一部移動してきたkoma.icon