Applying "Design by Contract" を読んだ
読んだやつ
Applying Design by ContractでGoogle検索して一番上に出てくるPDF
TL; DR
- OOPについてのソフトウェアの信頼性を改善するための方法論の一つ
- 信頼性はソフトウェアを作る上でしばしば考えるテーマ
- 信頼性を確保するための方法としてよく"防御的プログラミング"が挙げられる
- NPEを過剰に恐れてのif (concrete != null) とか
- これは本来不要なコードを増やしてソフトウェアの複雑性を増すので再考の余地がある
- 契約という考え方をソフトウェアに適用してシンプルにしてみる
- 何かしらのタスクの実行が他のサブタスクと関連する時、呼び出し側と呼び出される側の関係をきちんと規定する
- この条件をAssertions (表明) と表現する
- 表明 - Wikipedia
- 各ルーチンを作るときには"precondition (事前条件)" と "postcondition (事後条件)" を含めるのが大事
- Eiffelだと言語レベルでサポートしてるっぽい。よく論文内で出てくる
- Eiffel - Wikipedia
- preconditionはルーチンが正しく安全に動作するために必要な条件を示す
- 例えば、ある木構造データに対して新たにノードを追加するput_child(new)があったとして、newはnullじゃダメですよーみたいな感じ
- "特別なケース"(↑の例ならnewがnullなら別の処理をしたいなど)は事前条件に含める
- preconditionの違反は呼び出し側の実装バグであることを示している
- postconditionの違反は呼び出される側の実装バグであることを示している
- こうしたconditionsを前提としてソフトウェアを組み上げることで不要なテストの作成を減らす
- まあでもpreconditionもpostconditionもOOPに限った話じゃないよね
- OOPなら"class invariants (クラスの不変性)"を考えると良さげ
- 特定のルーチン処理を超えて全てのクラスインスタンスに適用できる不変的なプロパティ
- 例えば、ある木構造データであるノードcurrentを基準にその左ノードがnullでないとき、左ノードのparentプロパティは暗黙的にcurrentであるみたいな感じ
- この不変性は常に保たれている必要がある
- クラス生成時、クラスの受け持つルーチンの実行時など、またこれに限らないall provisions of the XXX code
- あるルーチンが継承によって拡張される場合はpreconditionはより広く、postconditionはより厳しくあるべきだとしている
- Aのpre, postに対して、Aを継承したBのpreconditionは「original precondition or new precondition」postconditionは「original postcondition and new postcondition」の論理式が真である必要がある
- 継承元のassertionsについては保持するべき(preconditionなら守るべきとは言っていない
- 継承元の不変性は継承先にも適用するべき
- 不変性は子になるに従ってより強いものになるか親と同じになる
- 例外処理も該当のルーチンが契約に基づいて設計されているのであれば"失敗"という概念を定義できる
- ルーチンが契約を満たせなかった時に発生する
- ハードウェアの不具合、実装上のバグ、外部からの期待しないイベントなど
- ルーチンが契約を満たせなかった時に発生する
- 失敗したらやるのは以下のどれか
- 再試行 (resumption)
- 失敗を呼び出し元に伝える (organized panic)
- Eiffelの例だとrescue句つかってる
- この例のgetintは外部機能で、こちらでコントロール出来ないもの
- 引数が不正な場合に例外を発生させる
- この例をRubyで書くならたぶんこんな感じ
def get_integer_from_user(n) failures = 0 begin result = getint(n) rescue failures = failures += 1 if failures < 5 then puts "Input must be an integer. Please enter again: " retry end end end
ぜんぜんTL; DRじゃない