Featured image of post Gradleのdependenciesはどう書くべきか

Gradleのdependenciesはどう書くべきか

現代のプログラミングは、半分インターネットでなされていると言っても過言ではないでしょう。言語やMWの公式ガイドラインだけではなく、ウェブ上の数多くのコミュニティで情報を得られる時代ですからね。そしてMavenやGradleのように、依存関係の管理自体がネットに繋がっていることを前提としているものもあります。私もそういうトレンドから離れてはなく、自分が書いているコードで問題が発生するととりあえず検索で調べてみる方です。時間はかかっても、大概はそうすることで解決できますね。

しかし、そんな便利ながらもネットで知識を求める行動にはリスクもあります。果たしてその情報が正しいかという問題ですね。まずコーディングに関する情報だと、私の基準では2年以上立っているものなら信じがたいものとなります。当時はそれが正解だったとしても、今はそうではない可能性がありますので。例えば同じライブラリーを使っているとしても、バージョンアップによりパッケージの構成が変わったり、メソッドのシグニチャーが変わったりしますが、ネットにある全ての情報がそのような変更まで全部反映しているとは思えません。実際動いたというコードが書かれているとしても、そのコードはあくまで普通のテキストであって、現在コンパイルして動かすことのできるものでもないですしね。

今回の主題であるGradleのdependenciesをどう書くべきかというのも、そういう意味でのものです。私自身も今まで依存関係を書くときは、公式で提案しているコードやブログなどを参照してコピペしていました。しかし、そうしていると同じライブラリーでもcomplieだったりimplementationだったりruntimeだったりしていて、かなり混乱するものでした。結局どんな書き方をとるとしてもその結果は同じように見えるのに、こうして区分している理由は何かと思いました。

そして結局、その疑問の答えは自分がとある問題に直面することで得られました。なので今回はただ単に理論の話ばかりではなく、問題が起こり得る場所とその解決法についてのものでもあります。

compile? implementation?

ネットでGradleで依存関係を書く方法を調べると、同じライブラリーでもその書き方がcompileだったりimplementationだったりしますね。現時点でそのうちどれを使ってもまず動きはするので、一見何の問題も内容に感じされます。

しかし、問題はcompileです。このキーワードは多分、依存関係を表現する最も古い記述法です。実際の検索結果が一番多いような気もしますね。意味的にも、このライブラリーをコンパイル時に使うのような感覚なのでわかりやすいと思います。

ただ、Gradleの4.7バージョンのJava Library PluginのDependency managementタブを参照すると、compileはDeprecated1と書いてあります。このポストを書く時点での安定化バージョンは5.6で、今後6.0が予定されているのでこちらはなるべく使わない方が良さそうです。

公式の文書を参照すると、compileimplementationapiの二つに分けられたらしいです。つまり、これからはcompileの代わりにそのどちらかを選ぶのが望ましいということですね。

implementationとapi

既存のcompileだと、必ず「依存関係の伝播」が発生していたらしいです。つまり、Aというライブラリーを使って新しくBというライブラリーを作成したとしましょう。そしてまた、Bに依存するCを作成します。こういった場合、CではBを依存することだけでAにも触れられるようになります。このような状況は、場合によってはまあり望ましくないことになる可能性もありますね。Aをラッピングして、仕様を絞る目的としてBを作成したとしてもCからAを直接扱うことができますから。これはJavaのカプセル化の観点からしてもあまり望ましくないです。

implementationでは、この依存関係の伝播に制約をかけています。つまり、BからAに依存するとき、compileではなくimplementationで記述するとCからAを直接参照できなくなるということです。この理由から、最近は多くの場合にcompileの代わりにimpelementationを使うことを推奨しているらしいですね。

それに対してapiでは従来通り依存関係の伝播が発生します。BがAに依存しているとき、完全なラッピングではなく、CからAも参照させたいならこちらを使うべきですね。実際、業務でライブラリーをいくつか作っていましたが、一部では大元のライブラリーを参照させる必要があるものもありました。この場合にimplementationを使うとCからのAに対する直接的な参照がGradleとしては認識できなくなったらしく、コンパイルでのエラーが発生することもありました。なのでdependenciesの記述では、自分が作成しているものの性質を正しく理解し記述方法を決めるということも大事ですね。

これらの関係を簡単な図として表現すると、このようになります。

Gradle Implemenatation API

最後に

これでシンプルに、Gradleのdependenciesはどう書くべきかについて述べてみました。実際はimplementationapi意外にもランタイムのみ参照のruntimeOnlyやテスト用のtestImplementationなど、様々な記述方法があるので、状況と場合によっては柔軟な対応が必要かと思います。ただ、大抵の場合は依存関係を整理し、implementationapiの使い分けを確かにすることが最も重要なことなのではないでしょうか。

また、先に述べたように、ネットから得られた情報がAPIの更新事項を確かに反映しているかのチェックも重要ですね。古い情報だと今のコードでは問題を起こす可能性がありますので。そういう意味では、このポストも時間がたてばいつか正しくない情報となる可能性はあります。いや、このポストだけでなく、もしかしたらこのブログ全体で私が書いている情報の全てがそうなのかもしれません。

コードを書きながら、ネットの情報を参照するときは常にそれが書かれた日付を確認し、公式のドキュメントと見比べる必要があるのではないかと思います。勉強も最新化の方針で!


  1. Deprecatedは、「オススメしない」という意味です。プログラミングの世界では、何らかの問題があったり必要が無くなったりするなどの理由でこれからなくなる可能性の高い関数を指す言葉となっています。実際、EclipseではJavaの関数に@Deprecatedをつけると関数名に打ち消し線が現れることを確認できます。 ↩︎

Built with Hugo
Theme Stack designed by Jimmy