SpotBugs(FindBugs)のWarningに対処する

SpotBugsについての投稿は/tag/SpotBugsにあるので参照されたい

SpotBugs(FindBugs)の警告への対処だが、四種類ある。これについて説明する。

サンプルの警告

こんなコードを書いてみる。

public class Sample {
  int a; 
  public Sample(int a) {
    this.a = a;
  }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof Sample)) return false;
    return ((Sample)o).a == a;
  }
}

別に問題は無いと思うのだが、SpotBugsは不平を言ってくるのだ。

foobar.Sample defines equals and uses Object.hashCode() [Troubling(14), High confidence]

何が気に入らないのかというと、equalsを定義しているのに、hashCodeを定義していないという。
こちらとしては必要無いのだが、FindBugsの方としては「必須」と決めつけているらしい。

SpotBugsのパースペクティブを見てみると、以下のような表示だ。説明と対処法が表示されている。

※ここでは英語で表示されているが、これはEclipseを完全に英語モードにしたがためである。Eclipse:完全に英語モードにする方法を参照のこと。

FindBugsおすすめの方法で対処する

先の説明には「必要無いならこれを入れろ」とある。その通りに、以下のようなコードを挿入する。

public int hashCode() {
    assert false : "hashCode not designed";
    return 42; // any arbitrary constant will do
}

あまり気分の良いものではない。

素直に修正する

ごく普通に対処する。この方法が最も一般的だろう。後からSampleをMapやSetに入れることに決定しても問題無い。

public int hashCode() {
  return a;
}

そもそも警告を出さないようにする

EclipseでSpotBugs(FindBugs)を使ってみる、その1で説明するように、検出するバグの種類を設定できるため、このバグの検出を排除してしまうことにする。

まず、このバグのタイプを確認する。

タイプが「HE」であることがわかる。パターンは「HE_EQUALS_USE_HASHCODE」というものだ。

次にEclipseもしくはプロジェクトについてのSpotBugsの設定を表示させ、このバグを検出させないようにする。

  • 左側のJavaの下にあるSpotBugsをクリック
  • Detector configurationタブを選択
  • Pattern(s)をクリックしてABC順に並べる
  • しかし、ここがわかりにくいのだが、Co|Eq|HEというパターン名なのでCのところにある。
  • FindHEmismatchのチェックをはずす

下の方は、このディテクタの検出するバグの説明である。hashCode()およびequals()がらみの問題を検出するとある。

そして、まさに今回の問題「HE_EQUALS_USE_HASHCODE」が含まれていることがわかる。

この状態で再度SpotBugsを走らせると、見事にバグ検出がなくなる。

その場所だけ警告を出さないようにする

以上述べた対処方法としては、「素直にhashCode()を定義する」「そもそもこの問題を検出させないようにする」だったのだが、この「一箇所だけ検出させない」ようにすることもできる。

通常のJavaのSuppressWarningsアノテーションと同様にSuppressFBWarningsアノテーションを使えば良いのだ(FBになっているのはFindBugs時代の名残と思われる)。

では、「何らかのライブラリを入れねばならないのか?」と言われるとさにあらず、自分で作ってしまってよい。SpotBugsはSuppressFBWarningsがどのパッケージであろうが気にしない。

適当なところに以下のクラスを作成する。

package mypackage;
import java.lang.annotation.*;
@Retention(RetentionPolicy.CLASS)
public @interface SuppressFBWarnings {
    String[] value() default {};
    String justification() default "";
}

そして、Sampleを以下に変更する。

@SuppressFBWarnings(value={"HE_EQUALS_USE_HASHCODE"}, justification="必要無いので")
public class Sample {
  int a;  
  public Sample(int a) {
    this.a = a;
  }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof Sample)) return false;
    return ((Sample)o).a == a;
  }
}

justificationは設定しなくてもよい。ここにはコメントを書くようだが、この内容がいつ使われるのかは不明。

参考

以下を参考にした。