イリュージョニストにならないために
前回は「クライアントにとって使いやすいAPI」について語りました。今回は「読みやすい実装」について。ネタ元は同じくSqlExecutor。
まず。javadoc厨で契約(仕様)原理主義の立場でいきなり厳しいことを言ってしまえば、「そもそもクライアントに実装を読ますなよ。javadocで全部伝わるように書け。」ってことになるのだが、まぁまぁ、そこまでスパルタンにはなるまい。
でだ。何か追いかけたい事があって、メソッドの実装を追いかけ始めた。そして、以下のようなコードに出会ったとする。
java.sql.Statement stmt = ...; java.sql.ResultSet rs = ...; ResultHandler handler = ...; String sql = ...; if (stmt.execute(sql)) { rs = stmt.getResultSet(); handler.handleResultSet(sql, rs); } else { int count = stmt.getUpdateCount(); if (count >= 0) { handler.handleUpdateCount(sql, count); } }
さてここで。「executeを実行したら、それに対して必ずhandlerが呼ばれるだろうか?」という視点でこれを見てもらいたい。
必ず呼ばれると思った方。素晴らしいですね。Statementの仕様をよく理解されています。「理解してたんじゃなくて調べたんだよ」という場合でも、素晴らしい。ここで仕様を調べにかかる、という姿勢が素晴らしい。
必ずしも呼ばれないかもしれないと思った方。恐らく、ごく一般的です。恐らく「executeの結果がfalseで、かつcountが-1だったら handlerが呼ばれないことがあるか…ふーん…」と思ったのだと思います。あなたはイリュージョンを見たのですね。
解説
種明かしをしてしまうと、Statement#execute()がfalseを返した場合は、Statement#getUpdateCount()は必ず0以上の整数を返します。executeがtrueを返した時は-1を返します。つまり上記のコードで、count >= 0 がfalseとなることはありえません。つまり、handlerは必ず呼ばれるんです。
execute | getResultSet | getUpdateCount |
---|---|---|
true | != null | == -1 |
false | == null | >= 0 |
しかし、この事は「Statementの仕様を把握しないと」見えてきません。上記の通り「handlerが呼ばれない経路、あるはずのないものが見えてしまう人」もいるのです、っていうかそういう人の方が多いんです(多分)。
また、executeがtrueの場合とfalseの場合の対称性が確保されていません。falseの時getUpdateCountが0以上であることをチェックするのであれば、trueの時にgetResultSetのnullをチェックすべきです。これも誤解を引き起こす原因のひとつ。
このような「イリュージョンを見せてしまうコード」は読みやすいとは言えません。
対応1
if (stmt.execute(sql)) { rs = stmt.getResultSet(); if (rs == null) { throw new Error("java.sql.Statementの挙動が仕様通りではない、つまりJava実装のバグ。通常は発生しえない。"); } handler.handleResultSet(sql, rs); } else { int count = stmt.getUpdateCount(); if (count < 0) { throw new Error("java.sql.Statementの挙動が仕様通りではない、つまりJava実装のバグ。通常は発生しえない。"); } handler.handleUpdateCount(sql, count); }
Statementが仕様と異なる動きをしたら、Errorをぶん投げます。まぁ、ここで例外じゃなくてErrorを選択したのは私のポリシーより。ここは論点じゃないので、別のポリシーを持っていたらRuntimeException系になるかもしれないです。
要は「あり得ないことが起きたのならば、クライアントに通知する」のが大事だと思った場合に取る選択肢。
対応2
if (stmt.execute(sql)) { rs = stmt.getResultSet(); handler.handleResultSet(sql, rs); } else { int count = stmt.getUpdateCount(); handler.handleUpdateCount(sql, count); }
ありえないこととして、そもそもチェックしないという選択肢。Java実装のバグをいちいち疑ってたら、エラいことになってしまうでしょう…。
というわけで、このような対応をすれば、イリュージョンを見せられることもなくなるでしょう。下手にクライアントを惑わさないこと。コレ大事。
// getMoreResultsについて考えなきゃなぁ。