Design by Contract (DbC)
事前条件、事後条件、不変条件、を契約せよ
契約による設計。プログラム設計の安全性を高めるための、設計手法です。
あなたが作るクラスやメソッドについて、それを使う人との間に、守るべき事項について契約を交わします。
契約を守ることにより、プログラムがおかしな動作に陥ることを防ぎます。
「契約による設計」で交わす3つの契約
プログラムのクラス/メソッドについて交わすのは、以下の3つの契約です。
- 事前条件 メソッド開始時に、呼ぶ側が保証すること
- 事後条件 メソッド終了時に、呼ばれた側が保証すること
- 不変条件 メソッド開始時および終了時に、オブジェクトが保証すべきこと
これらの契約により、クラスやメソッドの誤った利用を防止します。
事前条件-使う側が守る条件
事前条件 (precondition)は、メソッドを使うときに、使う側が守るべき契約です。
簡単に言うと、メソッド実行時に、ありえない条件を入力しない、ということ。
メソッドを呼び出すときの入力値には、特定の前提条件が必要な場合があります。
- この範囲の値しか受け付けられない
- この文字は空であってはいけない
例えば「平方根を求める」メソッドなら、その入力は0以上でなければなりません。(そうでないと計算できませんね)。
メソッドを呼び出す側は、メソッドが正常動作する入力を与えること。
事後条件-使われる側が守る条件
事後条件 (postcondition)は、メソッドの結果が返却されたときの契約です。
メソッドが呼び出しが完了したら、呼び出し側が欲しい情報を必ず返すこと。これを約束します。
当然その結果は、呼び出し元の求めるものとなっている必要があります。
「平方根を求める」メソッドなら、input=result*result
となっていることが、求める結果です。
メソッド側は、メソッドを呼び出した側に期待する出力を返却すること。
不変表明-常に守られるべき条件
不変表明 (invariant)は、クラスが常に保っておくべき性質、を約束するものです。
- オブジェクトの生成時
- メソッドを呼び出す前
- 呼び出した後
どんな場合においても、クラスがその外部に公開しているすべての操作について、保証されるべき性質です。
例えば、財務合計balance
は、常に内訳entry
の金額amount
の合計、であるべきです。
なので、balance == sum(entries.amount())
は、いつでも成立すべき不変条件になるわけです。
クラスは、公開するすべての操作について常に約束された状態を守ること。
契約による設計とは「クラスの説明書」を作る事
契約による設計のメリットとして、一般的に以下のようなものが言われます。
- 仕様の明確化 クラスの振る舞いを、明確に規定できる
- エラー早期発見 契約違反のチェックで、エラーを早期に特定できる
- 安全性強化 契約に沿って動作確認することで、堅牢性が向上する
まとめれば結局のところ、「契約による設計」の根元にあるのは、
クラスの「説明書」を作ること。
このクラスは、
- こういうことができるよ!
- こういう風に使ってね!
- こんなことしちゃダメだよ!
設計書があれば、それがどのようなクラスなのか、どう使うべきなのか、が明確になります。
「クラスの説明書」があれば、
- クラスを使う側:どのようにクラスを使えばよいか、理解しやすくなる
- クラスを作る側:どのようなクラスを作ればいいか、指針にできる
使う側、作る側の双方に、メリットがあるのです。
契約の実装方法は2種類
契約をプログラムで実装すること、それはすなわち、契約が破られないようにする仕掛けを作ること、です。
考え方は大きく2つ。
- 契約が守られているかをチェックする
- 契約を破るような実装ができないようにする
どちらも有効ですが、特に効くのは2番目です。
そもそも契約違反の実装ができないのですからね。
実装方法1:表明(assert)による契約のチェック
まず思いつく実装は、契約が守られているかを、都度のタイミングでチェックすること。
確認には「表明」(assertion)が使われます。
表明(assert)で契約違反をチェックする
表明とは、必ず成立すべき条件式です。falseとなるなら、それは契約違反を意味します。
「平方根を求める」メソッドなら、表明はつぎのとおり。
- 事前条件の表明 input ≧ 0
- 事後条件の表明 input=result*result
C,C++,C#,Java,Pythonなど、多くのプログラム言語では、assert
と呼ばれる機構が備わっています。
assert input = result * result
ほとんどの場合、assertは、false となった時点で即時にプログラムが停止します。
false、つまり契約違反となるのはバグであり、回復不可能だからです。
assertはバグ検出のために使う
上記からも分かるように、assertはバグを検出するためのもの。
通常発生しうる状態(バグでない状態)に、表明(assert)を使ってはいけません。
assertは、どのプログラミング言語でも「リリーズ時は無効とする」オプションが備わっています。
実装方法2:型(Type)を使って契約を守らせる
実装方法のもうひとつは、そもそも契約を破る実装が出来ないようにすることです。
プログラミングの型定義を駆使して、契約違反なコードが作れないようにします。
不変表明に、不変クラスを導入する
不変表明は、つまるところクラスメンバの状態に依存します。ならば、メンバを絶対更新できないようなクラスなら、必ず不変表明が守られるわけです。
このような、オブジェクト生成時からメンバを変更不可としたクラスを、不変クラス(イミュータブルクラス)といいます。不変クラスにすれば、それでもう不変表明が守られるわけです。
可能な限り、不変クラスでの実現を考えましょう。
事前条件/事後条件を、定められたインタフェースしか取れない型で守る
事前条件や事後条件は、このようなパターンが多いでしょう
- 特定範囲の引数しか入力できない(0以上のみ入力可能、など)
- 特定の状態のときしか約束した結果が返せない(開始状態のときのみ可能、など)
ならば、
- 特定範囲の値した取り得ない「型」を入力とする(PositiveNumber型を入力とする)
- 特定の状態をしめす「型」を分離してメソッドを作る(StandbyProcess型とActiveProcess型を分離する)
といったように、型の定義により契約を表すことで、契約はより頑健なものとなります。
契約による設計は、テストにも役立つ
契約による設計によって設計されたクラスは、ユニットテストによる品質確認も容易となります。
そう「契約が守られているか」をテストで確認すればよいのですから。テストケースも明確になります。テスト駆動開発との相性は抜群です。
契約による設計は、自動ユニットテストで、契約内容を常時確認できることが有効です。あちこちにassertを入れてコードを複雑にするよりも大事です。
まとめ
クラス設計時には、ついつい、その中身をどうするかに心を奪われてしまいがち。そうすると、使う側にとってはいまいち使いにくいクラスができあがります。
「契約による設計」を使うと、クラスを使う側の視点に立った設計ができます。あなたのクラスを強固なものにしましょう。
コメント