@numa08 猫耳帽子の女の子

明日目が覚めたら俺達の業界が夢のような世界になっているとイイナ。

プログラミング言語の例外

f:id:numanuma08:20170812195436j:plain 大抵のプログラミング言語に備わっている機能の1つが「例外」。

例外的状態を表現して例外からの回復やリソースの開放、エンドユーザへの通知に利用される機能。なんだけど、この「例外」という機能がいまいちしっくりこなくて、例外について考える夜が年に2〜3回ある。ここで客観的な例外の機能と自分の持っている疑問をまとめておこうかなぁと思った次第。

qiita に書こうかなとも思ったけど、「よくわかりませんでした」って書くとぶっ殺されそうな気がするのでやめる。

例外的状態ってなに?

例外的状態という言葉があって、この状態を表現する機能が例外だとされていると思ってる。で、例外的状態ってものが何なのかわからない。そこで例外的状態でない状態を考えて、それでないものが例外的状態とする。

例外的状態でない状態が何なのかと言えば、多分契約が満たされている状態なんだと思う。

契約プログラミング - Wikipedia

契約が満たされている状態は、 wikipedia からの引用をすると、

事前条件 (precondition) サブルーチンの開始時に、これを呼ぶ側で保証すべき性質。

事後条件 (postcondition) サブルーチンが、終了時に保証すべき性質。

不変条件 (invariant) クラスなどのオブジェクトがその外部に公開しているすべての操作の開始時と終了時に保証されるべき、オブジェクト毎に共通した性質。

これらを満たしている状態が契約を満たしている状態と言えるっぽい。コンテキスト、仕様によって事前条件や事後条件は変わってくるのだと思う。事前条件や事後条件、不変条件を満たしているにも関わらずプログラムが失敗をするのならそれは例外といえるのかもしれない。

ちょっと前に話題になった例外アドベントカレンダーからこの記事が気になった。

qiita.com

記事の後半の方でファイルのI/O処理に関して例外的状況になり得ることを示している。ここで例えをHTTP通信を行う実装について考えてみる。

あるURLへアクセスをしてHTTPのbodyを取得するプログラムだ。この事前条件は何だろうか。

  • そのURLのドメインが存在する、名前解決ができる
  • HTTPのコネクションを貼ることができるデバイスが提供されている
  • リクエストをするURLのパスやHTTPのヘッダー、body、その他のパラメータが間違っていない

何となくこんなところだろうか。それに対して事後条件は

  • 目的のデータが得られる

たぶんこれだけだ。

そうなると例外的状況はいくつか思いつく。

  • 接続中に何らかの理由で接続が遮断されてしまった。デバイスのエラーなど
  • サーバーに何かがあって正しいレスポンスが得られなかった
  • 存在していると思ったURLにデータがなかった

これらは全部例外的状態なのだろうか。「存在していると思ったURLにデータがなかった」は割とよく起こりうる。HTTP404のレスポンスが得られる場合だ。

しかし、HTTP通信を行う仕組みを作る上でHTTP404を想定しないでコードを書くだろうか?

例外的状態は場合によるのか

HTTP通信の仕組みを作るときにHTTP404を想定しないでコードを書く事がありえるのだろうか。

全体のシステムの中で「このURLには必ずデータがある」と仕様書などで定められていたならば、例外的状態になりえるようにも思う。逆に、とくにそう言った仕様がないのであれば例外なのか。

コードで書くとこうなる。

sealed class HttpResponse

data class Success(val responseCode: Int = 200,val header: Map<String, String>, val body: ByteArray)
data class Failure(val responseCode: Int, header: Map<String, String>, val body: ByteArray)

fun httpRequest(request: Request): HttpResponse {
  // 省略
}

// 顧客データを取り出す
// 顧客データはいつも /clients にあるし、サーバーは絶対にレスポンスをするのでそれ以外は例外
val client = (httpRequest(clientsRequest) as Success).body

// 商品データを取り出す
// 別システムが出力しないことがあるので、失敗を考慮
val response = httpRerquest(itemRequest)
when(response) {
  Success(_,_, body) -> {}
  Failure(code,_,body) -> {}

こういうこと?まじで?見ての通りコメントによる補強が必要な実装となった。もしもコメントが無かったらこの2つの処理でレスポンスの取扱が異なる理由を知るにはドキュメントを見返す必要が出てくる。(そして、往々にしてコメントは忘れられる)

1つの仕組みになっている方が良い気がする

結局今回の疑問はこういう「場合によっては例外だけど場合によっては例外じゃない」のは正しいのかどうか分からなくなった点。上の例だといつでもHTTPのレスポンスに対してSuccessなのかFailureなのかを想定したコードを書くのが適切だと思う。その結果、例外という仕組みは消え去った。

他にも例えばファイル読み書き時のIOExceptionなんて例外もある。OSから上がってくる割り込みとかどうすればええねん、って思う一方で「OSが割り込みをしてくることも有るし、それを想定したコードを書くべきなのでは?」とも思う。

結局、例外とはあるいは例外的状態とは何なのか、今のプログラミング言語で利用されている例外処理の機能は適切なものなのかが分からなくなったという話。