読者です 読者をやめる 読者になる 読者になる

この日記は私的なものであり、所属会社の見解ではありません。 GitHub: takahashikzn

assertノススメ

Java

僕は仕事だろうがプライベートだろうが、コードを書くときには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(); }

ってやっても、処理量はほぼ同一ですよ。

なんだか

引数チェックの話に終始してしまいましたが、
上記の『あり得るかも・あり得ないはず』ルールはどんなシチュエーションでも
適用できるはずです。