mike-neckのブログ

Java or Groovy or Swift or Golang

チェッカーフレームワーク(org.jetbrains:annotation)の使い方メモ

IntelliJ IDEAとかAndroid Studioを使っている人は、メソッドの横の方に@が書かれているのを見たことがあるかと思います。

f:id:mike_neck:20150829163350p:plain

(Android Studioで実際に表示されるのかは知らないけど、ツイッターで観測している限りアンドロイダーの人で「この@Contractって何」ってツイートしている人がいたのでおそらく表示されている)

ここにカーソルを合わせると、メソッドの性質について情報が表示されます。

例1:次のようなメソッドの場合には@Contract("null -> fail; !null -> !null")と表示されます。

  • nullを引数に渡すと例外が発生する
  • nullでない引数を渡すとnullでない値が返される

例2:つぎのメソッドの場合には@NotNullと表示されます。

  • どのような引数を渡しても必ずnullでない値が返される

この情報が表示されることで、あるクラスのあるメソッドの引数にnullを渡すと例外が発生するとか、List<UserInformation>を返すメソッドなんだけどnullを返してくることがあるっぽいといった性質がわかるので、事前にそれらの対策を織り込んだコードを書くことができると思います。

チェッカーフレームワーク

IntelliJ IDEAで表示されているのはJetBrains製のチェッカーフレームワークorg.jetbrains:annotationsに基いたメソッドの静的な性質についての情報です。

チェッカーフレームワークJSR308で提案されたもので、コンパイル時にnull安全性や副作用の有無などを検査するフレームワークです。

詳しくはこのあたりのサイトを読むといいと思います。

ワシントン大学のチェッカーフレームワークのサイト

The Checker Framework

で、そのレポジトリー

github.com

というか、日本語でかなり詳しく書かれた記事

waman.hatenablog.com

ちなみにチェッカーフレームワーク自体はJava SE8で導入されているものです。

JetBrainsのチェッカーフレームワーク

で、JetBrainsのチェッカーフレームワークに戻ると、このアノテーションセットはmavenセントラルに登録されているので、取ってくることができます。

<dependency>
    <groupId>org.jetbrains</groupId>
    <artifactId>annotations</artifactId>
    <version>13.0</version>
</dependency>
dependencies {
    compile 'org.jetbrains:annotations:13.0'
}

そして、@で表示されているのと同じ内容のアノテーションを記述すると、横にある@が消えます。

f:id:mike_neck:20150829171052p:plain

実はこんな偉そうなことを書いている割には、全部のアノテーションを使ったことがあるわけではなく、@Contract@NotNullくらいしかわかってないので、僕が知っている限りの書き方をメモしておきます。

@NotNull

  • メソッドに付与された場合は、そのメソッドnullを返さないことが保証されることを意味する
    • nullを返すようなコードを書いていると、コードハイライトされる
  • メソッドのパラメーターに付与された場合は、引数にnullを渡すことはできない
    • IntelliJ IDEA内でnullを渡すと、IllegalArgumentExceptionが投げられる
    • ただし、普通のjavacでコンパイルしたものだと、nullは渡せる
  • フィールドの付与した場合は、フィールドにnull値を設定することは許されない

@Contract

メソッドの性質に関する契約を記述するアノテーションで、boolean型の値pureString型の値valueを設定する

  • pureは副作用に関する契約を記述する
    • trueを設定した場合、メソッドを実行しても副作用が発生しないことを保証する
      • trueを設定して副作用があるコードを記述するとコードハイライトされる
    • false(デフォルト値)を設定した場合、メソッドに副作用があることを示す
  • valueメソッドの性質について記述する
    • メソッドの性質は引数部 -> 作用 ; 引数部 -> 作用 ; ...のような形で引数部 -> 作用;で区切った形で記述する
    • 引数部は引数 , 引数 , ...のように引数をカンマで区切った形で記述する
    • 引数には渡される可能性のある値を記述する。なお、引数には次の文字列が使用できる
      • _ : 任意の引数
      • null : null
      • !null : nullでない値
      • true : booleantrue
      • false : booleanfalse
    • 作用には返される値を記述する。なお、作用には次の文字列が使用できる
      • null : null値が返される
      • !null : nullでない値が返される
      • true : booleantrueが返される
      • false : booleanfalseが返される
      • fail : 例外が返されることを意味する
      • any : 任意の値 : 使うこともできるがあまり意味がない

例1. 二つの引数をとって、どちらともnullでない場合にはnullでない値が返され、いずれか一方がnullだった場合には例外が発生する、副作用のないメソッドについては次のように記述する。

@Contract(value = "null, _ -> fail; _, null -> fail; !null, !null -> !null", pure = true)

例2. 一つの引数をとるが、どのような値が渡されても絶対にnullが返されないメソッドの場合は次のように記述する。

@Contract("_ -> !null")

実はこれで良かったりもする。

@NotNull

例3. 引数なしのメソッドで、絶対にnullが返されないメソッド

@Contract("-> !null")

例4. 二つの引数を取り、最初の引数がnullの場合にはnullが返されるメソッド

@Contract("null, _ -> null")

javadocを読んでいるとその他のアノテーションにこのようなものがあります。

  • @Nls : 文字列がローカライズされる必要がある
  • @NonNls : 文字列がローカライズされる必要はない
  • @Nullable : 引数やフィールドにnullを渡しても大丈夫
  • @PropertyKey : リソースにあるプロパティキーに必ず含まれている文字列
  • @TestOnly : テストコードにのみ使用することが許されるメソッドコンストラクター/プロダクションコードでは使用してはいけない

使い方

ドキュメントとして使うのも、まあ、悪くはないんだけど、せっかくコードハイライトしてくれる(eclipsenetbeansのことは忘れている)のだから、コードを書く前にそのメソッドに求める性質を記述してから書くというのが妥当な使い方だと思います。

まず、メソッドに求める性質を記述します。

f:id:mike_neck:20150829180629p:plain

そして、性質を満たすようにコードを書きます。

f:id:mike_neck:20150829180727p:plain

すると、あら不思議!nullとか例外レベルのコード品質が保証されたコードが出来上がります(「品質」という言葉をここで使うのは、かなりためらっている)。

これで、nullとか例外に関するテストコードを書く手間が少し省けますね(あるに越したことはない)。


さあ、あなたもチェッカーフレームワークでADD(Annotation Driven Development)な生活を!

amazon.co.jp