【契約による設計】あなたのプログラムが守るべき3つの約束

design-by-contract
契約による設計

Design by Contract (DbC)

事前条件、事後条件、不変条件、を契約せよ

契約による設計。プログラム設計の安全性を高めるための、設計手法です。

あなたが作るクラスやメソッドについて、それを使う人との間に、守るべき事項について契約を交わします

契約を守ることにより、プログラムがおかしな動作に陥ることを防ぎます。

目次

「契約による設計」で交わす3つの契約

プログラムのクラス/メソッドについて交わすのは、以下の3つの契約です。

  • 事前条件 メソッド開始時に、呼ぶ側が保証すること
  • 事後条件 メソッド終了時に、呼ばれた側が保証すること
  • 不変条件 メソッド開始時および終了時に、オブジェクトが保証すべきこと

これらの契約により、クラスやメソッドの誤った利用を防止します。

事前条件-使う側が守る条件

事前条件 (precondition)は、メソッドを使うときに、使う側が守るべき契約です。

簡単に言うと、メソッド実行時に、ありえない条件を入力しない、ということ。

メソッドを呼び出すときの入力値には、特定の前提条件が必要な場合があります。

  • この範囲の値しか受け付けられない
  • この文字は空であってはいけない

例えば「平方根を求める」メソッドなら、その入力は0以上でなければなりません。(そうでないと計算できませんね)。

事前条件

メソッドを呼び出す側は、メソッドが正常動作する入力を与えること

事後条件-使われる側が守る条件

事後条件 (postcondition)は、メソッドの結果が返却されたときの契約です。

メソッドが呼び出しが完了したら、呼び出し側が欲しい情報を必ず返すこと。これを約束します。

当然その結果は、呼び出し元の求めるものとなっている必要があります。

「平方根を求める」メソッドなら、input=result*result となっていることが、求める結果です。

事後条件

メソッド側は、メソッドを呼び出した側に期待する出力を返却すること

不変表明-常に守られるべき条件

不変表明 (invariant)は、クラスが常に保っておくべき性質、を約束するものです。

  • オブジェクトの生成時
  • メソッドを呼び出す前
  • 呼び出した後

どんな場合においても、クラスがその外部に公開しているすべての操作について、保証されるべき性質です。

例えば、財務合計balanceは、常に内訳entryの金額amountの合計、であるべきです。

なので、balance == sum(entries.amount()) は、いつでも成立すべき不変条件になるわけです。

不変表明

クラスは、公開するすべての操作について常に約束された状態を守ること

契約による設計とは「クラスの説明書」を作る事

契約による設計のメリットとして、一般的に以下のようなものが言われます。

  • 仕様の明確化 クラスの振る舞いを、明確に規定できる
  • エラー早期発見 契約違反のチェックで、エラーを早期に特定できる
  • 安全性強化 契約に沿って動作確認することで、堅牢性が向上する

まとめれば結局のところ、「契約による設計」の根元にあるのは、

クラスの「説明書」を作ること

クラスの説明書

このクラスは

  • こういうことができるよ!
  • こういう風に使ってね!
  • こんなことしちゃダメだよ!

設計書があれば、それがどのようなクラスなのか、どう使うべきなのか、が明確になります。

「クラスの説明書」があれば、

  • クラスを使う側:どのようにクラスを使えばよいか、理解しやすくなる
  • クラスを作る側:どのようなクラスを作ればいいか、指針にできる

使う側、作る側の双方に、メリットがあるのです。

契約の実装方法は2種類

契約をプログラムで実装すること、それはすなわち、契約が破られないようにする仕掛けを作ること、です。

考え方は大きく2つ。

契約の実装方法の考え方
  1. 契約が守られているかをチェックする
  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を入れてコードを複雑にするよりも大事です。

まとめ

クラス設計時には、ついつい、その中身をどうするかに心を奪われてしまいがち。そうすると、使う側にとってはいまいち使いにくいクラスができあがります。

「契約による設計」を使うと、クラスを使う側の視点に立った設計ができます。あなたのクラスを強固なものにしましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次