utamaro’s blog

誰かの役に立つ情報を発信するブログ

JavaのOptionalを戻り値に使用することについて消極的な理由

僕は戻り値にOptionalを使用することに消極的です。

戻り値に使用する理由に対して以下の理由をよく聞きます。

  • メソッドを利用した際にnull値を返す可能性を明示する。
  • nullを考慮したコーディングを強制させる。

これらをやる理由が無いと考えているからです。

メソッドを利用した際にnull値を返す可能性を明示する。

これ本当に明示できているのでしょうか? Optionalだから明示できているのでしょうか? Objectでもnullが入る可能性があるのだから、Objectでもnullを返す可能性は考えられるのでは無いでしょうか?

以下の条件で使い分けるのだとするなら、Optionalを使用する利点が出てくると思います。

  • Optionalを返すメソッドではnullを返す可能性がある。
  • Optionalを返さないメソッドはnullを返す可能性が無い。

ただし、戻り値から値を取り出す場合にコーディングに差異が出てきます。

idと一致するオプションが無い場合Optionalを返すメソッドを例とします。

public Optional<OptionDetail> fetch(Integer id) {
    if (id == 1) {
        OptionDetail optionDetail = new OptionDetail().setId(id).setName("SAMPLE");
        return Optional.of(optionDetail);
    }
    return Optional.ofNullable(null);
}

このとき呼び出し元は以下のように実装します。

OptionDetail optionDetailA = fetchDetailA(1)
        .orElseThrow(() -> {
            System.out.println("log message");
            throw new IllegalArgumentException("invalid id.");
        });
optionDetailA.getId();

データがない場合はログを出して、例外を出して処理を終えます。 .get()orElse(T)を利用しないのは、nullというのは、データがない状態を示しているからです。 データが無いのに、その場合の値を置き換えるとかおかしいですよね? .get()を使用した場合、値がnullだった場合にNoSuchElementExceptionが発生します。

次にOptionalを使用しない場合です。

public OptionDetail fetchDetailB(Integer id) {
    if (id == 1) {
        OptionDetail optionDetail = new OptionDetail().setId(id).setName("SAMPLE");
        return optionDetail;
    }
    return null;
}

このとき呼び出し元は以下のように実装します。

OptionDetail optionDetailB = fetchDetailB(1);
if (Objects.isNull(optionDetailB)) {
    System.out.println("log message");
    throw new IllegalArgumentException("invalid id.");
}
optionDetailB.getId();

見ればわかると思うのですが、対して違いが無いんです。

個人的には戻り値が全部Optional<T>となっているよりObjectとなっていた方が良いと思ったり、でもいちいちif文を書くの面倒だなとも思います。

そもそもを語るのなら、nullを返すメソッドはNullPointerExceptionを発生させる可能性があるため避けるべきです。 それを考慮した場合のメソッドは以下のようになるでしょう。

public OptionDetail fetchDetailC(Integer id) throws IllegalArgumentException {
    if (id == 1) {
        OptionDetail optionDetail = new OptionDetail().setId(id).setName("SAMPLE");
        return optionDetail;
    }
    throw new IllegalArgumentException("invalid id.");
}

データがない場合に例外を出します。 「存在しないデータを取得しようとした」という意味です。

利用側では以下の実装になります。

OptionDetail optionDetailC;
try {
    optionDetailC = fetchDetailC(1);
} catch (IllegalArgumentException e) {
    System.out.println("log message");
    throw e;
}
optionDetailC.getId();

Optionalの場合も同じ様な実装になります。

OptionDetail optionDetailD;
try {
    optionDetailD = fetchDetailD(1).get();
} catch (IllegalArgumentException e) {
    System.out.println("log message");
    throw e;
}
optionDetailD.getId();

どちらも同じ様な実装になるので、どっちでもいいんですよ。

ただ、Optionalの場合はorElseorElseThrowを利用できないので利用する必要性がありません。 また、fetchしてから一度.get()しないとなりません。 なにかデータを取得するメソッドを実行しているのに、なぜ何かを取得するメソッド(.get())を再度実行するのでしょうか?

IDEによってはisPresent()を利用してくださいと警告がでて混乱する可能性があるので使わないほうが良いでしょう。

これを考えると、この場合はOptionalを使わない方がマシです。

結論

  • Optionalの利用云々によってコーディングは対して違いがない。
  • nullを返さない実装の場合はOptionalは処理を煩雑にするため使用しない。
  • そもそもnullを返す実装を疑い、メソッドの利便性を考えたときにOptionalを使うメリットは小さい

nullを考慮したコーディングを強制させる。

本当にnullを考慮したコーディングを強制させるのでしょうか?

僕はそんなことは無いんじゃないかと思ってます。

.get()した場合try-catchを書かなくてもコンパイルは通ります。 そのままデプロイした場合NoSuchElementExceptionが発生する可能性があります。 それはNullPointerExceptionと何が違うのでしょうか。

Optionalを返すということはnullを返す可能性があると考えられるんだ!」

「だから.get()ではくorElse()orElseThrow()を使用しろ!」

orElseを利用するなら、なぜデフォルト値をメソッド側が返さないのでしょうか? orElseThrowを利用するなら、なぜメソッド側でIllegalArgumentExceptionなどの例外を出さないのでしょうか?

それはOptionalを利用するメリットかもしれませんが、Optionalを使わなければならない理由にはなりません。

だからOptionalについての議論が起こったりするんでしょうね。

nullを返すことに理由がある場合、なぜnullを返すのかjavadocにかかれているはずです。 そうなるとnullを返す可能性があるからと言ってjavadocすら確認しないということは無いでしょう。 そこに重要な情報が書かれていた場合のことを考えると、戻り値だけを見てコーディングするのは危険です。

(処理内容をコードから読み解く必要性はないです。必要がある場合はメソッド名が明確ではありません。)

また、nullを返すことに理由がない場合はそのメソッドの見直しが必要です。 理由のないnull返却は開発者を混乱させます。

最終的には利用するメソッドがどんな条件でnullや例外を出すのか確認して実装することになると思います。

そうなった場合、コーディング時のメソッドの使い方を確認するコストに差異はありません。 そして、それを確認したときOptionalを使用していなくても、エンジニアはそれに適した実装をします。

結論

  • nullを考慮したコーディングを強制するわけではない。
  • Optionalを利用してもしなくてもコーディング時にやることに変わりはない。

最後に

以上の理由から僕はOptionalを利用することに消極的です。

Optionalはメソッドを利用する際の処理を煩雑にしますし、実装方法によっては開発者を混乱させます。

また、javaの場合古い書き方に慣れている人のほうが絶対数が多く、Optionalの実装になれていない可能性があります。 チーム内で使い方の議論をして時間を浪費するぐらいなら使わない方が良いです。

そんなことよりも、よりよい機能について提案したり、リファクタリング作業や、業務の効率化にリソースを使ったほうが良いです。

Optionalの利用について議論をしていたり、考えている方の参考になれば幸いです。