契約プログラミング
契約プログラミング(けいやくプログラミング、英: Contract programming)または契約による設計(けいやくによるせっけい、英: Design by Contract; DbC)は、オブジェクト指向に基づくソフトウェア設計のための方法論である。
モジュールのインタフェースについて、モジュールおよび利用者が満たすべき条件を仕様として表明することで、モジュール/利用者の責任の所在を明瞭にし、設計の安全性を高めることを目的にしている。DbC における「契約 (contract)」とは、モジュール提供者から利用者に提示される事前および事後条件を指す。
DbC はバートランド・メイヤーにより提唱された[1][2][3]。
概要
「契約による設計」(DbC)における中心的な概念は、クライアントとサプライヤ[4]の契約 (contract) である。DbC における契約とは、クラス[5]のインスタンス[6]とそのメソッド[7] の利用に関する条件を形式的に表明したものであり、クラス不変条件とメソッドの事前条件および事後条件で構成される。これらの表明が一般的な表明と異なる点は、クライアントに対して公開されていることで、クライアントとサプライヤの両者に表明を遵守する義務が生じることである。
表明を予め記述しておくことで、実装者はその表明に従ってプログラムを記述することができる。一般に表明は静的な記述であり、必ずしもプログラム動作時に意味を持つものではないが、DbC をサポートするプログラミング言語では、プログラム実行時に表明を違反していないか監視することができる[8]。実行時の表明違反の監視に関連して、例外機構を利用することにより、実行時の表明違反を例外としてクライアントに処理させることができる。
クラス A が、クラス B に関するエンティティ(ローカル変数の定義やメソッド呼び出し)の記述を含む場合、B は A に対するサプライヤ、A は B に対するクライアントとなる。クライアントとサプライヤは同一クラスであってもよく、例えば this を介したメソッド呼び出しが相当する。
事前条件
事前条件 (precondition) は、メソッド開始時に保証されるべき条件の表明である。これはメソッド単位で表明される。具体的には以下が関係する:
- メソッドに渡される引数
- メソッド開始時のサプライヤクラスのインスタンスの状態についての条件
事前条件を満たすことはクライアントの義務になり、メソッド本体部分においては事前条件が満たされていない場合を考慮しなくてよい[9]。 これはサプライヤの利益と解釈できる。サプライヤは事前条件への作業から解放される。 もし事前条件が満たされていない場合は、処理系の設定によるが、メソッド本体の実行開始前にエラーとすることができる。
事前条件にサプライヤ・インスタンスの状態についての条件を含めることもできるが、その場合でもそれを満たすのはクライアント側の責任である。
事後条件
事後条件 (postcondition) は、メソッド正常終了時に保証されるべき条件の表明である。これはメソッド単位で表明される。正常終了とは、例外スロー終了やエラー発生終了ではないことを指す。具体的には以下になる。
- メソッド開始時のサプライヤクラスのインスタンスの状態
- メソッド正常終了時のサプライヤクラスのインスタンスの状態
- クライアントに渡す返り値
事後条件を満たすことはサプライヤの義務になり、もし満たされた場合は事前に決められた状態遷移が果たされて、これはクライアントの利益になる。クライアントは事後条件への作業から解放される。
不変条件
クラス不変条件[10] (class invariant) は、クラスが持つ公開[11]された各メソッドの開始時と正常終了時に共通して保証されるべき状態についての条件である[12]。ただしコンストラクタ[13]の呼び出しに関しては、事後条件としてのみ適用され事前条件として保証する必要はない[12]。(引数や返り値などを制約するメソッド個別の事前/事後条件と異なり)不変条件はインスタンスの状態にのみに対する表明である[14]。インスタンスの「状態」はそのインスタンスのすべてフィールドの値によって決まるため、より具体的には、不変条件はフィールドの値に関する表明となる[14]。
不変条件は公開メソッドの事前条件および事後条件として暗黙的に追加される[14]。 不変条件を持つクラスに関して、そのクラスの公開メソッドの呼び出しの際、クライアントはメソッドの事前条件とサプライヤ・クラスの不変条件の両方を満たす義務がある。 またサプライヤは、事前条件(と不変条件)を満たしたメソッド呼び出しに対して、メソッド終了時にそのメソッドの事後条件と不変条件の両方を満たす義務がある。
義務と利益
契約でのクライアントとサプライヤの、プロセス(process)およびステート(state)は、それぞれの義務(obligation)と利益(benefit)に例えられている。
- クライアントの義務:サプライヤ・インスタンスのフィールド値のチェックと、有効なパラメータ値を渡すこと。これは事前条件を満たすプロセスとされる。
- サプライヤの義務 :メソッドの決められた働きと、正常終了時に決められた状態遷移を果たすこと。これは事後条件を満たすプロセスとされる。サブクラスで実装が与えられる仮想メソッドを考えると分かりやすい。
- クライアントの利益:メソッド正常終了時の決められた状態遷移と、あるならばリターン値を受け取ること。これは事後条件からのステートとされる。
- サプライヤの利益 :必要なサプライヤ・インスタンスのフィールド値が不適正か、渡されるパラメータ値が無効ならば、メソッドを実行しなくてよいこと。これは事前条件からのステートとされる。
契約における例外の扱い
契約の主要な目的は、各メソッドの実行(例外可能性)実行中止(例外未発生)正常終了(例外可能性)異常終了(例外スローとエラー発生)を特定して、バグ責任の所在を明らかにすることである。そこでは例外(exception)のハンドリングが重視されている。
正常終了には、途中の例外発生とその解決継続も含まれていることに注意する。無効なパラメータ値やフィールド値の不適正によるメソッドの実行中止は、同時に例外も未発生と解釈されることに注意する。
やや分かり難いサプライヤの利益の「メソッド未実行=その時その範囲での例外未発生」は有効な責任特定材料になる。例外が解決できなかったら異常終了になる。異常終了は最も容易な責任特定材料になる。正常終了には例外発生の解決継続も含まれるのでやや複雑になる。例えば契約の入れ子で、内側契約の例外が外側契約で解決されていた場合などは見落とされがちなので要注意になる。
契約の継承
契約による設計ではクラスの継承がフォローされている。適切な継承は、スーパークラスの契約を保持しながら、そのサブクラスによる柔軟なパフォーマンスをもたらせる。スーパークラスの契約に対するそのサブクラスの適用は、動的バインディングと同義になる。これは開放閉鎖の原則に沿っている。
スーパークラスの契約と、そのサブクラスの契約の間には明確な整合性が要求される。サブクラスの契約の改変許容範囲は、以下のように定められており、この法則はbehavioral subtypingとも言われる。それに沿って派生された契約下のサブクラス・インスタンスは、スーパークラスの契約下でも安全に用いることが出来るとされる。
- 事前条件は、サブクラスで弱める(weaken)ことはできるが、強めることはできない。
- 事後条件は、サブクラスで強める(strengthen)ことはできるが、弱めることはできない。
- 不変条件は、サブクラスでもそのまま維持されなければならない。
- スーパークラスの例外から派生した例外を除いては、サブクラスで独自の例外を投げてはならない。
事前条件の弱めるは様々な意味を持つが、一例としてはパラメータ型の汎化である。事後条件の強めるも様々な意味を持つが、一例としてはリターン型の特化である。
契約プログラミングをサポートする言語
出典
- ^ Meyer, Bertrand: Design by Contract, Technical Report TR-EI-12/CO, Interactive Software Engineering Inc., 1986
- ^ Meyer, Bertrand: Design by Contract, in Advances in Object-Oriented Software Engineering, eds. D. Mandrioli and B. Meyer, Prentice Hall, 1991, pp. 1–50
- ^ Meyer, Bertrand: Applying "Design by Contract", in Computer (IEEE), 25, 10, October 1992, pp. 40–51, also available online
- ^ クライアント(顧客)とサプライヤ(供給者)の用法については例えば Meyer 1990, pp. 30, 101 を参照。邦訳では顧客/供給者が用いられているが本項ではクライアント/サプライヤを用いる。
- ^ DbC の文脈における「クラス」の定義は例えば Meyer 1990, p. 85 を参照。この定義ではクラスは静的な型として表現され、それ自体はオブジェクトと見なされない。また同書ではしばしばクラスを「モジュール」と表現する。Smalltalk の流れを汲むクラスの扱いに関しては例えば Meyer 1990, p. 138 を参照。
- ^ Meyer 1990 では基本的に「オブジェクト」の語を用い、あらわにインスタンスとは呼んでいない。クラスを型と見なす場合、クラスのインスタンス以外にオブジェクトは存在しないため (Meyer 1990, pp. 85, 139) 混用しても問題はないが、一般的な文脈を優先して本項ではインスタンスの語を用いる。
- ^ Meyer 1990 では「メソッド」の代わりに「ルーチン」を用いているが、本項ではより一般的な前者を用いる。「メソッド」は Smalltalk 由来の同義語として Meyer 1990, p. 589 に示されている。
- ^ Meyer 1990, pp. 196–199.
- ^ Meyer 1990, pp. 159–160, 163–164.
- ^ Meyer 1990, p. 168 では「(クラス)不変表明」と読んでいる。
- ^ Meyer 1990, pp. 113, 170, 273–274 では「エクスポート」と表現している。
- ^ a b Meyer 1990, p. 170.
- ^ Meyer 1990, pp. 103, 170 では「Create プロシージャ」と表現している。プロシージャの定義は Meyer, pp. 109–110 を参照。
- ^ a b c Meyer 1990, p. 171.
参考文献
- Meyer, Bertrand『オブジェクト指向入門』酒匂, 寛(訳); 酒匂, 順子(訳); 二木, 厚吉(監訳)、アスキー出版局、1990年11月21日(原著1988年)。ISBN 4-7561-0050-3。