契約書に捨印を押す - オブジェクト指向と型システムの狭間で例外を考える その2

 オブジェクト指向と型システムの狭間で例外を考える - プログラマーの脳みその続き。

 僕は不勉強なのでメイヤー氏の思想というものをそれほどトレース出来ていない。だから開放閉鎖原則についての哲学のようなもの、というのはデザインパターンから嗅ぎとったもので、誤りがある可能性が高いということをあらかじめ断っておく。間違いは指摘してもらえると嬉しい。

検査例外は開放閉鎖原則に反しない

まず、検査例外は発生したその場、もしくは直接の呼出し元で処理しない限り、throws に記述せざるを得ない。

そうしない場合、より上位層の throws を追加する必要が出てくる。このような追加、もしくは変更は、中間のクラスの再リリースという手間も必要となる。

これは、明らかに開放閉鎖原則に違反する。

例外について色々と考えてみた - ぐるぐる~

「検査例外はアジャイルやオブジェクト指向の考えに反するという事実」について一部誤解あり - じゅんいち☆かとうの技術日誌
タイトルは釣り度が強すぎかなー、、、まぁ、個人のブログなんで気にしないでくださいw

ブログはカジュアルに書けばいいんですよ。タイトル付け失敗してもサーセンです。
オブジェクト指向の考え」ではなく「オープンクローズド原則」に反するとしたほうがいいですね。

非検査例外に萌えるわけ - じゅんいち☆かとうの技術日誌

あたりの話題。

 先に脇にそれておくけども、「ブログはカジュアルに書けばいい」というのは全く賛成なのだけど、しかし技術系の話題で誤った情報を訂正もなく晒すのはどうかとは思う。何かについて考えて発表する、そこから議論が発展する、それはいいのだけど、結論として最初の説が誤りだったらば、それは訂正しなくてはいけないと僕は思う。

 というわけで、ややしつこく感じられるかもしれないけど誤りだと思うところはツッコミを入れさせてもらいます。人に恨みがあるとかそういうわけじゃなくて、説に用事があるってところをご理解いただければ幸いです。

 さて、「検査例外」という「存在」が「開放閉鎖原則」=「オープンクローズド原則」に反するのか、という話だけども、僕はちょっと違うと思う。

 あるメソッドが例外をthrowするように改変した、ということそのものが「閉鎖」を破っているじゃないか、というわけだ。開放閉鎖原則に則ってインターフェース(ここではJavaのinterfaceではなく境界面といった意味合いだ)の修正は行わない、修正について閉じている、というのであれば、そのメソッドがそもそもthrowする例外が検査例外だとしてもなんら問題にはならないはずだ。

 中間のクラスが〜という話題は、開放閉鎖原則を破って境界面に変更を加えた場合に話であって、検査例外が開放閉鎖原則を破るわけじゃない。

 と、このあたりがどうにもこうにも気になったんだ。

契約の更新

 契約に基づくプログラミング、つまるところメソッドのシグニチャというかインターフェース(境界面)を契約として守るようにその両側、メソッドの呼び出し元とメソッドの実装を作るという考えと、型システムというものは密接に関わっている。

 が、乖離もある。

 契約の更新を型システムが検出出来ない、あるいは契約の更新をプログラマも認知しない、ということがありうる。知らないうちに契約が更新されて、トラブルのもととなるということがある。熟練のプログラマはそうした隙間をよく識っていて、そうした隙が生まれないように注意深くプログラミングするが、そこに完璧を求めるのはヒューマンエラーってものをまるで理解していない三流プロマネだけにしてもらいたい。いや、そんな人はプロマネなんてしないでもらいたい。

 例を挙げよう。

public enum Status {
    RUN,
    STOP,
}

という列挙型を戻り値とするメソッドgetStatus()を考えよう。

public Status getStatus() {
}

このメソッドの戻り値はRUNないしSTOPという値をとる。そうした定数を返す、という仕様、つまり契約だとする。

 ここでStatusにINITという値を追加する。

public enum Status {
    RUN,
    STOP,
    INIT,
}

 この場合、メソッドもその呼び出し元もプログラム上はなんらコードを書き換えない。そしてコンパイルしなおしてもコンパイルエラーにはならない。

 しかし、契約は更新された。呼び出し元は新たにINITに対応する処理を記述しなければならない。していなかった場合にはバグとなりうる。しかも高確率で。

 メソッドというインターフェース(境界面)が型システム的には変化していないが、境界面の両側の責務が変更になっているのだ。契約に基づくプログラミングというのは型システムが見逃したらならそれを見逃してもいいという教えでは決してない。型システムが検知出来なくとも契約が更新になっていれば、境界面の両側に修正が必要となる。

 そして境界面の「かたち」については変更を行わない、classやinterfaceのextendsで新たなclassを作ることで拡張を自在に行えるように設計せよ、というのが開放閉鎖原則だった。

捨印という考え方

 つまるところ、契約の更新ってのは大変なのだ。更新に際してのヒューマンエラー的な見落としをできるだけ避けるためには機械的な警告というか、検査例外のような型システム的な補助というのは有用だと言える。しかし、情報を隠すなら情報の中、必要な情報を大量のゴミの情報に埋もれさせることもまた全体をそっくり無視するというヒューマンエラーの元になってしまう。オオカミ少年的なアラームは逆効果だ。

 契約書の修正にいちいち顔を突き合わせて訂正印を押すような面倒は嫌だが、白紙委任状みたいなのも怖い、ちょうど捨印ぐらいの手頃な訂正方式がないと検査例外って堅苦しくていけないよっていうのが今の評価ではなかろうか。

 捨印ってのは

あらかじめ押す訂正印であり、以後の訂正を認める意思表示である、と解釈される。

一度作成した文書を訂正するには、たとえ微細な訂正であっても、訂正個所に訂正印を押さなければならない。訂正印の無い修正は無効であるし、文書の有効性が損なわれる事も考えられる。しかし、訂正の都度文書を交換・送付などして訂正印を押すのは煩雑であるし、状況によっては日数や時間も要するなど効率も悪く、迅速な処理の妨げとなる。

そこで、微細な誤記、あるいは明らかな誤字脱字程度であれば、相手に断ることなく訂正して良いと承認を与える意思の現れとして、捨印を押す。

捨印 - Wikipedia

 非チェック例外多用作戦のトレードオフ認識 - 都元ダイスケ IT-PRESS非検査例外に萌えるわけ - じゅんいち☆かとうの技術日誌で語られているトレードオフは現時点のJavaを使ってどうにか作るという今直面している現実でのトレードオフ。そのトレードオフについては同意するところだしなんら反論はない。

 メソッドの形を変える、つまり契約に基づくプログラミングの契約を変えるにあたっての手間とどう戦うか、というのは現場では重大な関心ごとなんだ。

まとめ

 しかし、検査例外が開放閉鎖原則に反するってのは誤解を招いて不当な評価がされる元になる。ちょっといただけない。

 検査例外は閉鎖を破ったときに手間がかかって面倒くさい、ちょっとした契約の更新に際しては捨印みたいに簡単に済ませれるようならいいのにねって話だということでいかがだろう?