Murga

個人的に言いたいコト・主張・気持ち。

契約による設計・契約プログラミングが少しワカッタ

「契約による設計 Design By Contract」とか「契約プログラミング Programming By Contract」とか、単語は聞いたことあったけど何するもんなのかよく分かんねーなーと思ってた。

Wikipedia の記事を抜粋するとこんな感じ。

プログラムコードの中にプログラムが満たすべき仕様についての記述を盛り込む事で設計の安全性を高める技法。

契約は、コードの利用条件が満たされることによって成立する。 それら条件は、満たすべきタイミングと主体によって、以下の3種類に分けられる。

  • 事前条件 (precondition)
    • サブルーチンの開始時に、これを呼ぶ側で保証すべき性質。
  • 事後条件 (postcondition)
    • サブルーチンが、終了時に保証すべき性質。
  • 不変条件 (invariant)
    • クラスなどのオブジェクトがその外部に公開しているすべての操作の開始時と終了時に保証されるべき、オブジェクト毎に共通した性質。

何かをコードに入れることでどうにかすんだろーなー、とは分かるが、何を書くことを指しているのかがよく分かっていなかった。

そしたらコチラの記事を見つけた。

とても分かりやすい。

つまりこういうことだ。

  • 事前条件 (Precondition) : 引数チェックしろってこと。
    • IllegalArgumentException を投げる実装でも良いが、ひととおり実装が終わった後に「想定外の値」が飛んでこない確証があるのであれば、アサーション (assert()) で書いおいても良いと思った。
    • アサーションの良いところは、開発中のビルドではコードが残るのでチェックできるが、プロダクション・ビルドの時はアサーションのコードが除去されるので、実行速度が落ちないという点。ただし、もしもその上で例外が発生した時は、スタックトレースが追いづらくなる恐れはある。
    • 関数を呼ばれた側が、関数のド頭で引数チェックをする実装が基本。事後条件と並べて「入力」と「出力」を保証できる。入力仕様はその関数の作りによって決まるだろうから、呼ばれる側に実装しておけば間違いない。
    • 関数を呼ぶ側が、呼ぶ直前に設定する引数をチェックするような実装もアリみたい。このやり方の注意点は、呼び出す関数の仕様が変わった時に、許容しているはずの引数のパターンをアサーションエラーにしてしまうような「修正漏れ」が起きそうなところ。
  • 事後条件 (Postcondition) : return する値の仕様チェック。
    • 「この関数では計算結果が負数になることはない」とか「データがない場合は空の配列を返す、null は返さない」みたいなことが決まっていれば、それを return の直前assert() しておく。
    • アサーションで実装し、単体テストと結合テストが済んでいれば十分だとは思うが、別に例外をスローするような実装にしても問題はなさそう。
    • 関数を呼んだ側が、受け取った戻り値を検証するような実装は見かけなかった。
  • 不変条件 (Invariant) : クラスのプロパティの値を変更する時にチェックする。
    • クラスの関数を呼ぶ前、呼んだ後で変わらない条件のこと。なので、全ての関数の最初と最後で、同じ条件をチェックする、というのが最も律儀なやり方。
    • そのような不変な条件って、クラスのプロパティの仕様ぐらいなので、プロパティに値を代入する直前にチェックすれば良い、と考えられる。
    • this.num = num; の直前に assert(num > 0) とチェックしたり
    • this.itemArray.push(newItem); の直前に assert(newItem != null) とチェックしたり
    • setText(text) のような Setter 関数のド頭でチェックしたり
    • という登場位置が妥当。

なーんだ、コレってほとんど普段から俺がやってることじゃん。と思った。アサーションは言語によっては使わないけど、関数の頭で引数チェックしてるし、関数の最後で戻り値のチェックしてるし、Setter 系の処理の所では引数チェックと同じノリでチェックしてるし。コレが契約プログラミングだったのか。

防衛的プログラミングとの違い

似たような言葉で、防衛的プログラミングという考え方もある。コチラは以下の記事で要領を掴めた。

起こりそうな例外を予めチェックしておき、例外を発生させないというプログラミング手法だ。

関数の引数チェックでは、null などの異常値を空文字に変換して処理を続行させたり。処理中の例外は catch して、何らかの初期値に差し替えて処理を終了させたり。

このようなコーディングもよくやってる。JavaScript の場合は nullundefined のような Falsy な値のチェックと変換が必要になることが多いので、そういう癖がついたと思う。

言われなくてもやってたわ

こんなの「契約プログラミング」だの「防衛プログラミング」だの、大層な名前を付けずとも当たり前にやってることだろ、と思ったんだけど、違うのかしら。「概念を知らないと意識できない」とは思うが、僕はこれらの手法の概念を理解していなかったのに、「こうやれば色々防げるでしょ」って思い付いていた。ということは誰でも思い付く程度のことだろう、と思うんだけど、違うのかしらねぇ。