僕は仕事だろうがプライベートだろうが、コードを書くときにはassert文を積極的に使います。
しかし、自分の他に使っている人をほとんど見たことがない。…何故?
考えるんじゃない 感じるんだ
使わない人が使わない理由として一番挙げそうなのが『いつ使えば良いか分からない』。
でもこれ、個人的には簡単。『使いたい時に使えばいい』
だってassertは正式なコードじゃないんだから。自由に使えばいい。
じゃ、『使いたい時』ってなんだよ?
という問に対して答えるならば。。。
例えば、コードを書いている時に
いまこの時点で、abcの値は1のハズだけど、マルチスレッドで動作した場合にもちゃんと動作するかな…
まあ、コード上は正しいはずだし、いちいち
if (abc != 1) {
throw new IllegalStateException("abc expected 1 but was " + abc);
}
って書くのも(メンドイ|コードが見辛くなる)からいいや。
とか、ふと思うことありますよね?
...え?ない?アナタはウソツキです。
コレがまさにassertの使いどころなのです。
具体例
僕の使い方はこんな感じ。
public class FooLogic { // 公開API public void doSomething1() { final List values = new ArrayList(); values.add(this.calcValue()); this.evalAll(values, new Function() { public void eval(Object value) { .... } }); } // 公開API public void doSomething2() { final List values = new ArrayList(); while(this.isConditionGood()) { values.add(this.calcValue()); } this.evalAll(values, new Function() { public void eval(Object value) { .... } }); } // 非公開API private void evalAll(Collection values, Function f) { assert (values != null); assert (f != null); for (Object value: values) { assert (value != null); f.eval(value); } } private Object calcValue() { ... } private interface Function { void eval(Object value); } }
ここは、
private void evalAll(Collection values, Function f) { if (values == null) { throw new IllegalArgumentException(...); } else if (f == null) { throw new IllegalArgumentException(...); } for (Object value: values) { if (value == null) { throw new IllegalArgumentException(...); } f.eval(value); } }
と書くべきじゃないのか、という意見があります。
確かにコレでも間違いではないのかもしれませんが、僕はコレ、冗長だと思います。
だって、foreachはpublicじゃない。つまり公開APIではないので
使う人は『解っていて使う』わけです。
だから、本来なら『不正な引数を渡される』ということは有り得ないはず。
でも、もし
- FooLogicを複数人で編集しているとしたら…
- doSomethingの数が数十個あるとしたら…
そのうちいくつかでミスが出てくる可能性もあるわけです。
だから、念のためassertしておく。これが僕の典型的な使い方。
冗長なのが問題なのか?
いえ、違います。(反語)
僕の考えでは、
- 公開API:公開しているのだから、どんな引数を渡されるか分からない。IllegalArgumentExceptionなどのチェックも含めて公開API仕様である。→あり得るかも
- 非公開API:使う人は、分かっているのだからそもそも引数チェックする必要はない。→あり得ないはず
です。公開APIの引数チェックと非公開APIのassertは意味が異なるということです。
だから、
import static ArgChecker.checkNotNull; public class FooLogic { ... private void evalAll(Collection values, Function f) { checkNotNull(values); checkNotNull(f); for (Object value: values) { checkNotNull(value); f.eval(value); } } }
などとやって冗長さを排除したとしても、
チームで開発するシチュエーションならば、やはり良くないと考えています。
引数チェックすべきタイミングの指針を、明確に打ち出せなくなるからです。
(※全メソッドで必ず引数チェックせよ、というルールはナンセンスだし)
想定問答 その2
『assert文だと、起動オプションに"-ea"を付けなくちゃいけないじゃないか』
そうですね。付ければいいのでは。
パフォーマンス?
if (...) { throw new Exception(); }
ってやっても、処理量はほぼ同一ですよ。
なんだか
引数チェックの話に終始してしまいましたが、
上記の『あり得るかも・あり得ないはず』ルールはどんなシチュエーションでも
適用できるはずです。