日々常々

ふつうのプログラマがあたりまえにしたいこと。

getとfindの使い分け

メソッド名の getXXXfindXXX どっちがいいの?みたいな話になることがある。

この手の話ができるだけでもいい感じだと思います。名前が記号化していないってことなので。 世の中には名前に力を割くのが無駄な文脈もあって、そう言うのに晒されて続けると当然そこに力をかけなくなります。 その文脈では最適解だけど、私は名前が重要だと思っているし、その価値観を土台に他のものを積み上げていきたい。 ということで話を戻す。

「得る」と「探す」のようなものを意図して使い分けるとコードが読みやすくなります。 使い分け方によって読みやすさは変わりはするのですが、「意図して使い分けている」だけでも十分変わります。 その意図に共感できたり汲み取れたりすればさらに読みやすくなりますが、その前段階として意図の有無が重要だと思ってます。

私の基本的な使い分け

  • getHoge(条件): Hoge
  • findHoge(条件): Optional<Hoge>

戻り値型に現れます。getOptionalはない感じ。

getXXX は有ることが前提。無い場合は例外的事象として例外で扱います。 このメソッドの呼び出し側に見つからない場合を考慮させるべきではないので、非検査例外とします。

findXXX は無い場合が例外的事象ではないので、戻り値で扱います。 戻り値 Optional として、呼び出し側にハンドリングを要求します。

いま null にするのは?

私が null 根絶穏健派(過激派じゃないよ)なので「使うな」にはなるんですが。

上記のような文脈の使い分けをするとして、見つからない場合に null を返すのは避けることを推奨します。 getHoge(): HogefindHoge(): Hoge が並ぶと、使い分けが分かりません。 この場合はドキュメントでのフォローになりますが、コンパイル時の検査が効かなくなり、Javaを使っている利点が減衰してしまいます。

事実 null を返すAPIはドキュメントでフォローされています。 たとえば java.util.Map#get(Object) が挙げられ、このメソッドを使用する側は null を考慮した実装をコンパイラではなく人間の注意深さでフォローする必要があります。 NonNull のようなアノテーションとか静的解析、IDEのチェックなどで機械的なチェックがフォローされる範囲は広がってはいますが、まだまだ。

Optional がない時代、 getXXX は同じで findXXXnull を返すか検査例外のいずれかで、慣習的には null が返されていたものが多かったように思います。 いまは Optional があるのでこちらで扱いましょう。完璧な解ではないですが、 null よりは良いです。

よそ見: Optional自身のgetの使い方

Optional には find とかないですが、 getempty の場合に非検査例外を投げます。

ただ get だけの名前だと危険が伝わりづらいため orElseThrow(Supplier)を使いましょう と書きました。こちらはJava8の頃(2015年5月)でしたが、 Supplierの記述がだるいのは理解できます。それもあってか、引数なしの orElseThrow() メソッドがJava10(2018年3月)で追加され、 Optional のJavadocでもこちらを使うように案内 されています。

なんとなく get メソッドがあるクラスで Optional を連想したのであげてみたけど、 Optionalfind は合わなさそう。もし使われてたとしたら、 find はメソッド呼び出し時に解決しにいくようなイメージを持ちそう。

よそ見: Jacksonのgetとfindの使い分け

Jacksonの JsonNode を見てみましょう。Javadocはこちら。以下のようなメソッドがあります。

  • get(String fieldName): JsonNode
  • findValue(String fieldName): JsonNode

いずれも JsonNode を返しますし、いずれも null を返します。違いは get が直下のみを探すのに対し、 findValue は子要素も探すこと。スキーマ不定でないなら基本的に get のみを使用するで良いと思います。

Jacksonのこの使い分けには「探す場所として指定されたパスに子要素も含むでしょ」のようなJSONがパスで表現できることを前提にしたAPIって感じですね。 そのライブラリがどういう世界観で言葉選びをしているかの過程を持っていると予測可能性が上がります。

よそ見: XXX

「よそがこうしているから自分たちもこうする」と言うのは「よそ」がいくら権威的なものであろうと微妙です。 よそはよそ。

でも「よそは(こう言う文脈だから)こう使ってるんだなぁ」と想像したり説明を試みてみるのは役立つと思います。 開発で使う語彙は中学英単語よりも少ないと思うし、目に入ったら眺めてみるといいと思う。

他の名前の候補

get でも find でもない何かいい感じの名前が見つかるならそれを試してみるのもいいです。 search はたまにみる。

読みやすいコードのガイドライン 第2章 命名 では get find search pop calculate fetch query load などが挙げられています。これらの単語で置き換えできるかを試してみると名前が引き締まったりする。

書籍では「辞書や類語辞典」が挙げられていて、私はこういうのは英英辞典をよく使います。

search を使うのは find より探し回る感とか検索条件が広そうなイメージある。とは言えネイティブでない我々がニュアンスで使い分けるのは難しいので、チームやそのコードに関わるスコープで会話しながら調整したいところ。

参考

先に挙げた「読みやすいコードのガイドライン」や「良いコード/悪いコードで学ぶ設計入門」など、最近(といってもともに2022年ですが)の書籍でも命名はそれなりに割かれています。

同じようなことや逆のことを書いているように見えたりして面白くあります。私も共感するものもあれば、趣味に合わないなぁと思うところもしばしば。この2冊はGod/Badのような書き口。

名前の話は長年ずっと語られることで、10年遡ってみても「CleanCode」(邦訳2009年)では第1章「クリーンコード」の次にくる第2章が「意味のある名前」です。

あと「きのこ本」として知られていた「プログラマがしるべき97のこと」(2010年)の「名前重要」とか。

20年、30年遡ってもいくらでもあると思うんで、興味ある人は探してみよう。

前述のように英単語の選択に迷う場合は英英辞典ですが、用途的にポケットサイズのを使っています。Webでもいいのですが、周りの単語がヒントになることも多いので紙が好き。

あとニュアンスはこちらのニュアンス図鑑もいい感じです。 make build create produce とか。

ニュアンスだとコアイメージもたまにみてる。

別に英語的な正しさとかは重視しているわけではなくて、意図して使い分けるための根拠を私はこの辺に求めている感じです。 日本語名称の時は国語辞典のほか、語源とか漢字の由来とかを参照したりする。そいやこっちは書籍とか持ってないな……。

私はいい名前が思いつかないときは変な名前をつける - 日々常々 とブログで書いているように、名前はいきなりは思いつかないしどんどん変えて育てていくものだと思うのでプロセスも紹介しておく。

scrapbox.io

このプロセスの過程で一般的には「避けるべき」とされる名前が出ることもあるだろうし、すべての名前をこのプロセスでやるものでもないと思っている。

と言うことで、名前の使い分け

意思に基づいて名前が使われていることを望みます。

それは「同じものは同じ単語を使う」と言う形で現れるかもしれないし、他の形かもしれない。 ともかく意思に基づいていることが重要で、「可読性」がもたらす「予測可能性」を重視したいと思っています。 読みやすいコードとは驚きのないコード。驚きのないコードとは予測が当たるもの。そんな感じ。

get/find の本稿の使い分けはそれなりに受け入れられていると思うので扱いました。 こうしておくとコードを読むときに挙動の予測ができて紛れが減ります。 可読性に寄与すると言われる一般的なルールに反していても、予測可能性があれば読みやすいコードと言えると思う。

使い分けに迷ったら自身の経験を言語化してみたり、他のライブラリの使い方を参考にしてみるのもいいと思う。 でもその使い分け方が貴方のコンテキストに合うかは別の話。あくまで参考に。 Jacksonの例であげたように文脈が違えば使い分けで示したいものも変わってくるので、ライブラリやフレームワークを使う時にはその文脈の言葉をどこまで持ち込むかを設計する必要がでてきます。ものによってはコンテキストマッピングが必要になるかもしれない。

そんな感じ!(まとまらない)

追記

「基本的な」使い分けと書いている通り、他の使い分けをすることもあります。 重要なのは使い分け方が同じ文脈で混ざらないことかなーって。 多分言葉を尽くせば根源にある共通的な概念を言語化できるんだけど、しなくても伝わるでしょ、うん。

計算量

私も同じ感覚ある。

件数

私も同じ感覚ある。 get1 に対して findList0..n と言う感覚。 件数での使い分けを考えると 0..1, 1 0..n 1..n を分けたくなって、 get / find だけじゃ足りねぇとなり、戻り値型とか findOne とか他の表現方法組み合わせてなんとかしようってやり始める。毎回何だかんだそれなりに落ち着くんだけど、決まった答えは持ってないかも。