前提条件を破った場合、どのような挙動をするのか?

「Nullチェックされている前提の処理」とJavadocに書いたとき、「throws NullPo…」は書くんだろうか。んー、コード上は発生しうるけど、実際発生しないから不要なのかな

はてなブックマーク - u1tnkのブックマーク / 2009年12月29日

まぁ、このブログで書いている話は、あくまでも「俺式」ということをご理解いただいた上で。(ここに書いた事が全て、って訳じゃありません。他にも色々ポリシーはあると思うが、自分はこれが一番良いと考えている。)

自分の考えは、契約プログラミングに基づいてます。DbC(Design by Contract)って奴ですね。

あるメソッドが「Nullチェックされている前提の処理」というのは、引数にnullは入ってこない前提、ということですよね。そしてJavadocを書くということは恐らくこのメソッドは公開API(publicかprotected)だ。可視性については、可視性と公開APIと非公開(内部)APIと - 都元ダイスケ IT-PRESS参照。非公開APIJavadocを書いてはいけない訳ではないのだが、その場合はExceptionではなく、Errorを飛ばし*1、「メソッドやクラスの仕様」ではなく「ライブラリの仕様」として「このライブラリのバグ*2に遭遇した場合はErrorが飛ぶ」というドキュメントをつけるようなことをする。書く場所は、このライブラリのトップレベルパッケージ(org.jiemamyとか)のpackage-info.javaあたりかな。

さて、公開APIを使うのは自分(のプロジェクト)だけではないと自分は考えている。誰がどのように呼ぶかわからない。つまり「今自分が見えている範囲」では確か呼ぶ側で事前にnullチェックがされているかもしれないが、「自分がそのプロジェクトから離れた後」にnullチェックしないままこのメソッドを呼ぶような処理を書くかもしれない。また、この成果物がライブラリ化され、「自分の与り知らない所」からnullチェックしないまま呼ぶかもしれない。ということを考慮する。

公開APIで「実際発生しない」というのは「自分が見えてる範囲でたまたま」発生しないだけだ。publicという単語には「共同で使うための、公共の」という意味がある。法律上の問題*3は別次元として、publicな型やメンバはもう「みんなのもの」なのだ。

現代は4つのアクセスレベルでの可視性制御の限界が囁かれていて、打破するためにいろいろ模索しているところ

はてなブックマーク - Nagiseのブックマーク / 2009年12月24日

余談だが、確かに影響が閉じる範囲が「クラス内(private)」「パッケージ内(default)」「全世界(protected,public)」というのはちと乱暴なのかも。defaultとprotectedの間に、もう少し広い「非公開API」があっても良いと思う。例えば「同じjar内」とか。こういうことを実現しようとしているのが、JAM(JSR 277)の話だったりOSGiの話だったりするのだろう。

閑話休題。引数がnullではない、というのはこのメソッドが動作する前提条件*4だ。この前提条件をどのように仕様化するか、いくつかの状況を考えてみた。

状況1: 暗黙の前提条件

今まで、現場で「暗黙の前提条件」というのを頻繁に見て気ました。まさにこのnullの例が多く、作った人に聞いても「いや、このメソッドはnullが渡されない前提だから」って…。なら書けw まぁJavadocがそもそも無い(=仕様がない)のだからムリだよなぁ。まぁこういった「暗黙の前提」が一番タチが悪い。

/**
 * 入力文字列を装飾して返す。
 *
 * <p>ただし、入力文字列の長さが3未満の場合は元の文字列をそのまま返す。</p>
 * <p>修飾とは、入力文字列の前後に以下の文字列を付加することである。</p>
 * <ul>
 *   <li>前に付加する文字列 "*・゜゚・ *:.。..。.:*・゜"</li>
 *   <li>後ろに付加する文字列 "゚・ ・*:.。. .。.:*・゜゚・*"</li>
 * </ul>
 *
 * @param src 装飾対象の文字列
 * @return 装飾済みの文字列
 */
public String decorate(String src) {
  if (src.length() < 3) {
    return src;
  }
  return "*・゜゚・ *:.。..。.:*・゜" + src + "゚・ ・*:.。. .。.:*・゜゚・*";
}

余談だが、上は「Javadocから実装が書ける」ことを意識したJavaodcになっていると思う。自分が昔よくやってしまっていたのは、「一番最初の1行しか書いてない」という状況w

状況2: 前提条件は示されている

そして、まぁマシなのはJavadocに上記のような「前提」が書いてある場合。まぁ渡しちゃったからNullPointerException(以下NPE)が飛んだんだな…、と推測できる。ただ、あくまでも推測だ。仕様はガッチリ固定されているべきで、使う側に推測させると意識のズレが発生する。明確に示すべきだと思う。

/**
 * 入力文字列を装飾して返す。
 *
 * (略)
 * <p>Nullチェックされている前提の処理なので、引数にnullを与えてはならない。</p>
 *
 * @param src 装飾対象の文字列
 * @return 装飾済みの文字列
 */
public String decorate(String src) {
  if (src.length() < 3) {
    return src;
  }
  return "*・゜゚・ *:.。..。.:*・゜" + src + "゚・ ・*:.。. .。.:*・゜゚・*";
}

状況3: 前提条件が崩れている時の挙動が示されている

「引数はNullチェック済み前提」ではなく「引数にnullを与えたらNPE」つまり「@throws NPE 引数にnullを与えた場合」とだけ書くと良いと思う。これで前提条件を示すこともできるし、崩れた場合の挙動も示すことができる。

/**
 * 入力文字列を装飾して返す。
 *
 * (略)
 *
 * @param src 装飾対象の文字列
 * @return 装飾済みの文字列
 * @throws NullPointerException 引数に{@code null}を与えた場合
 */
public String decorate(String src) {
  if (src.length() < 3) {
    return src;
  }
  return "*・゜゚・ *:.。..。.:*・゜" + src + "゚・ ・*:.。. .。.:*・゜゚・*";
}

状況4: で、俺式では

/**
 * 入力文字列を装飾して返す。
 *
 * (略)
 *
 * @param src 装飾対象の文字列
 * @return 装飾済みの文字列
 * @throws IllegalArgumentException 引数に{@code null}を与えた場合
 */
public String decorate(String src) {
  if (src == null) {
    throw new IllegarArgumentException();
  }
  if (src.length() < 3) {
    return src;
  }
  return "*・゜゚・ *:.。..。.:*・゜" + src + "゚・ ・*:.。. .。.:*・゜゚・*";
}

NPEをIAEに切り替えた。なぜか?

まずは抽象度の問題。「これを装飾しろ」に対して「nullを参照する変数にアクセスしてしまった!」という結果は、なんか会話が成り立っていないと感じる。自分は「これを装飾しろ」に対して「引数がおかしいよ!」と答えることで、会話が成立すると思う。

「飯くおーぜ」→「破産しちまった!」って会話が微妙にズレてませんか? 「飯くおーぜ」→「金ねーんだ、スマン」が妥当じゃなかろうか (例がイマイチだが…)

もう一つは、NPEってのはプログラマの不注意で結構頻繁に発生する例外だからだ。無意識にNPEが飛ぶことはあるが、無意識にIAEが飛ぶことはない。

別に注意力をもっと持てという訳じゃない。人間なんだから、絶対にやっちまう事がある。だから、俺式では「NPEは、飛んだらその時点でバグ」と位置づけてしまっている。乱暴に感じるかもしれないが、こうするとバグが早期に発見できる。つまり、@throws NullPointerException は書かない。書くと仕様になってしまい、バグではなくなるから。

まとめ

  • 「前提条件は何か」ではなく「前提条件を破った時の挙動」を書く。
  • 公開APIは「世の中全てのコードから、可能性のある全ての状況で呼ばれている」と考える。
  • コード間に自然な会話を成立させるような設計を目指す。
  • NPEが飛んだらバグ。


繰り返しますが、俺式ルールです。

あわせて読みたいhttp://mt.mizba.net/archives/001291.php

*1:assert文を使います。

*2:クラス内やパッケージ内に閉じているのだから、ライブラリのバグのハズ。

*3:著作権や特許等の知的財産権的な話。

*4:こういうのも事前条件と言えるんだろうか。自身のクラスが満たしているべきステートの条件のみを事前条件と呼ぶのだろうか。この辺り俺曖昧。