Java コードリーディングのコツ

この記事は 品川 Advent Calendar 2019 の5日目です。
OB 枠での参加です。いったいどこの某弊社なんでしょうか… 。

本記事は Eclipse で Java ライブラリのコードリーディングを行うときのコツをまとめます。あくまでも「自分はこんな考え方やテクニックを用いてコードを読んでいますよ」という内容なので、より良い方法があればコメントください。

検証環境

  • Java 8
  • Eclipse 2019-09 R (4.13.0)
  • Maven 3.3.3

はじめに

コードを読む方法は 静的/動的 の 2 通りあると考えている。状況に応じて使い分けることで、それなりの時間でコードの全体像を把握できるようになる。

  • 静的 = エディタや IDE を利用してコードを読む
  • 動的 = デバッガを利用して実行しながらコードを読む

ソースコードのダウンロード

何はともあれソースコードを用意する必要がある。Eclipse では maven と連携して、ライブラリのソースコードを自動でダウンロードできる。

f:id:kimulla:20191204193256g:plain

ソースコードが見れるようになった状態で maven の ローカルリポジトリを見ると -sources.jarが落ちてることが分かる。

f:id:kimulla:20191204194938p:plain

ソースコードが落ちてこない場合はライブラリを右クリックして maven > Download Sources をクリックする。

コードを静的に読む

調査の基点となるクラスやメソッドが明確なときは、IDE やエディタを利用すると良い。やみくもに読み始めても挫折するので、対象処理は限定するほうがよい。

調査の基点となるクラスやメソッドを明確にする方法として、次の方法がある。

調査対象を明確にする

公式リファレンス

例えば Spring Framework のトランザクション処理に関するリファレンスを見る。すると、次のような記述がある。

The key to the Spring transaction abstraction is the notion of a transaction strategy. A transaction strategy is defined by the org.springframework.transaction.PlatformTransactionManager interface, which the following listing shows:
引用 Spring リファレンス

この記述から PlatformTransactionManager が主要なインタフェースっぽいなと気づける。

Javadoc を読む

例えば @RequestMapping の Javadoc を見る。

Both Spring MVC and Spring WebFlux support this annotation through a RequestMappingHandlerMapping and RequestMappingHandlerAdapter in their respective modules and package structure. For the exact list of supported handler method arguments and return types in each, please use the reference documentation links below:
引用 Spring Framework API Document RequestMapping

すると RequestMappingHandlerMapping を読んでみようかなという気持ちになる。

ちなみに Eclipse なら F2 で見れる。

f:id:kimulla:20191204224645g:plain

例外時に出るスタックトレース

何かしら例外が起きてその原因を調査したい、というときは例外のスタックトレースを見る。

Exception in thread "main" java.lang.RuntimeException: sample exception
    at com.example.codereading.DemoApplication.sampleFunction(DemoApplication.java:21)
    at com.example.codereading.DemoApplication.main(DemoApplication.java:26)

例外のスタックトレースからは、例外発生時の関数の呼び出し関係がわかる。関数の呼び出しごとにスタックが上に積まれていくので、まずは上から(例外が発生した箇所に近い関数から)見ていくのが良いと思う。行数も出てるし、非常に便利。

今回の例だと、DemoApplication.java の 21 行目をまず見ることになる。

ログメッセージ

たいていのライブラリは、主要な処理の前にログメッセージを出してくれる。ログレベルと出力フォーマットを設定すれば、どのクラスで処理されているか処理の概要を押さえられる。

たとえば Spring Boot の場合は application.properties を設定すれば(logging.level.org.springframework: DEBUG)ログレベルを変更できる。
参考 Spring Bootのログ設定を変更する

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.0.RELEASE)

2019-12-04 18:52:32.572  INFO 8980 --- [           main] com.example.codereading.DemoApplication  : Starting DemoApplication on kimura-pc with PID 8980 (C:\Users\pbreh_000\eclipse-workspace\demo\target\classes started by pbreh_000 in C:\Users\pbreh_000\eclipse-workspace\demo)
2019-12-04 18:52:32.574  INFO 8980 --- [           main] com.example.codereading.DemoApplication  : No active profile set, falling back to default profiles: default
2019-12-04 18:52:32.574 DEBUG 8980 --- [           main] o.s.boot.SpringApplication               : Loading source class com.example.codereading.DemoApplication
2019-12-04 18:52:32.627 DEBUG 8980 --- [           main] o.s.b.c.c.ConfigFileApplicationListener  : Loaded config file 'file:/C:/Users/pbreh_000/eclipse-workspace/demo/target/classes/application.properties' (classpath:/application.properties)
2019-12-04 18:52:32.628 DEBUG 8980 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@63a65a25
2019-12-04 18:52:32.647 DEBUG 8980 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
...

ログ(DefaultListableBeanFactory : Creating shared instance of singleton bean ... )から DefaultListableBeanFactory が Bean を作っているのかなぁと想像することができる。

パッケージやクラス名から推測する

よく訓練された人はパッケージ名やクラス名から、だいたいの役割がわかると思う。自分は訓練されていないので、あまりやらない。たまに同一インタフェースの実装クラスを複数見比べるときに、パッケージを直接見るときがある。

f:id:kimulla:20191204190732p:plain

実行してスタックを確認する

リファレンス見ても分からない。例外スタックトレースも出ない。ログも見当たらない。そんなときに仕方なくこれを実施する。やりかたは後述する。

静的に読むときのテクニック

クラスにジャンプ(Ctrl + Shift T)

調べたいクラスをさくっと開ける。

f:id:kimulla:20191204204709g:plain

* などを使って部分一致検索もできる。

f:id:kimulla:20191204204732g:plain

アウトラインの表示(Ctrl + o)

クラスの変数やメソッドを一覧表示して全体像をつかむ。

f:id:kimulla:20191204204753g:plain

文字を入力すると一覧表示の対象を絞れる。

f:id:kimulla:20191204210557g:plain

クラス内の文字列検索(Ctrl + F)

調べたい処理名やパラメータが分かってるときに使う。

ctrl+f.gif

リッチな検索(Ctrl + H)

機能が豊富なため、細かい部分は割愛する。
参考 ひしだま's ホームページ Eclipseでファイル検索

とくに重宝してるのは、該当クラスを利用してる箇所を一覧表示するやつ。クラスにフォーカスをあてて Ctrl + H すると、入力フォームに埋まった状態になるため便利。

f:id:kimulla:20191204205509g:plain

型階層を表示(F4)

Java はオブジェクト指向なので型階層は見ておいたほうがいい。とくにインタフェースについては、実装に入り込まずに継承しているクラスの振る舞いを確認できるため、コードリーディングする上での重要度は高い。

f:id:kimulla:20191204205832g:plain

宣言にジャンプ(F3)

宣言にジャンプできる。

f:id:kimulla:20191204205845g:plain

実装にジャンプ(Ctrl + T)

オーバライドされたメソッドに対しては F3 じゃなくて Ctrl + T を使う。どの実装に飛ぶかはパッケージ名やクラス名から推測する。カンが外れるときもある。そういうときは実行して確かめる。

f:id:kimulla:20191204205927g:plain

呼び出し元を表示(Ctrl + Alt + H)

メソッドを呼び出してる箇所を一覧にできる。

f:id:kimulla:20191204211515g:plain

ジャンプ前に戻る(Alt + ←)

前述のジャンプ系を使っていると、元の場所に戻りたいときがある。そんなときは Alt + ← で戻れる。 Alt + → で進める。

f:id:kimulla:20191204211555g:plain

実行して動的に確認する

次のようなときは、実行して確認すると良い。

  • 該当処理がどこで行われているか分からない(アノテーションを多用したフレームワークはとくにこれが多いと思う)
  • どの実装クラスが選ばれるか分からない
  • どの分岐処理が実行されるか分からない

デバッガを使う

あやしそうな箇所にブレークポイントを設定し、デバッグモードで実行する。

たとえば @ResponseStatus(HttpStatus.CREATED) で HTTP ステータスコードを設定する機能がある。これがどうやって動いているのかわからないとする。

そういうときは一番プリミティブな関数(例えば Servlet コンテナ上で動いてるんだから Servlet API を使っているだろう)というあたりをつけてブレークポイントを設定する。ここから呼び出し元にたどっていけば、なんとなく処理を追えるようになる。

f:id:kimulla:20191204212111g:plain

どの実装クラスが選ばれるかわからないときは利用側のクラスにブレークポイントを打ち、 Variables を見ればよい。どの分岐に進むかわからないときも、ステップ実行(Step Into/Step Over/Step Return)していけばよい。

f:id:kimulla:20191204220419g:plain

またデバッグモードでは、値を変更することができる。

f:id:kimulla:20191204221334g:plain

そしてなんと、例外を発生させることもできる。 f:id:kimulla:20191204221020g:plain

これらを活用することで、コードをスムーズに追うことができる。

さいごに

自分なりにコードリーディングのコツをまとめてみた。これらを活用すれば、ある程度は効率的にコードが読めるようになると思う。

他にも良いテクニックがあったら、ぜひ共有してください!