SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

JavaのバグをFindBugsで見つける

オープンソースのJava向け静的分析ツール

  • X ポスト
  • このエントリーをはてなブックマークに追加

FindBugsの使用例: CheckstyleやPMDを超える機能

 静的分析ツールはオープンソースにも商用ソフトにも数多くありますが、FindBugsもその1つです。Javaの開発には、他にCheckstylePMDの2つがよく利用されます。Checkstyleは、命名規則やスペースの用法、Javadocsの有無といったコーディング標準に重点を置いた、従来型のツールです。PMDは、ベストプラクティス、最適化されていないコード、潜在的なエラーに重点を置くツールです。性格上、どの静的分析ツールにも似たような機能がありますが、これはCheckstyleとPMDについては特に顕著です。この2つほどではありませんが、PMDとFindBugsにも共通する部分があります。

 FindBugsが得意とする分野を理解するために、具体的な例を見ていきましょう。自治体がペットの犬の登録に使うデータベースを実装すると仮定します。次のクラスは、メインドメインのオブジェクト、つまりペットの犬を表します。

public class Dog {

    private String name;
    private String breed;
    
    // 以下、getterとsetter
    //...
}

 次に、プロジェクトの途中で、複数のDogインスタンスを1つのセットに配置することにした、などの理由から、equals()メソッドの実装が必要になったとします。ただし、equals()メソッドを正しく実装するのは厄介な仕事です。経験の浅いプログラマであれば、なおさらでしょう。以下に、上出来とはいえないDogクラスの実装例を示します。

public class Dog {

    private String name;
    private String breed;


    @Override
    public boolean equals(Object obj) {
        Dog otherDog = (Dog) obj;
        if ((otherDog.name.equals(this.name))
            && (otherDog.breed.equals(this.breed))) {
            return true;
        }
        return false;
    }
}

 この実装には複数のコーディングエラーがあり、アプリケーションがクラッシュしたり、予期しない動きを見せる可能性があります。特に、指定されたオブジェクトがDogクラスのインスタンスではない場合、このメソッドはClassCastExceptionを生成します。また、オブジェクトがnullかどうかをチェックしていません。さらに、equals()メソッドをオーバーライドしたのに、それに対応するhashCode()のオーバーライド実装がありません。Java言語では、同一のオブジェクトは同一のハッシュコード値を返すことが求められます。これが守られないと、高い確率でHashMapsとHashTablesは正常に動作しません。したがって、equals()メソッドをオーバーライドするときは、常にhashCode()メソッドもオーバーライドする必要があります。このルールはベテランのJava開発者には常識ですが、経験の浅い開発者は見逃してしまいがちです。

 この実装には、Javadocコメントがないなど、雑なコーディングの習慣も見られます。

 この記事で前に触れた静的分析ツールは、どれもこのコードについて複数の問題点を検出します。たとえば、Checkstyleは次のエラーを指摘します。

  1. Javadocコメントがない。
  2. パラメータobjはfinalであるべき(メソッド内でobjの値を変更しないため)。
  3. equals()の定義に対応するhashCode()の定義がない。

 最初のエラーはコーディング標準の問題、2番目のエラーはコーディングのベストプラクティスの問題、そして最後のエラーは潜在的なバグであり、ベストプラクティスとしても知られるものです。このエラーレポートには、Checkstyleが全般的にコーディング標準と良質のプログラミング習慣の徹底に重点を置いていることがよく現れています。

 PMDは、ベストプラクティスをより重視します。このコードをPMDでチェックすると、次の問題が指摘されます。

  1. メソッドの出口は1箇所のみにし、メソッドの最後のステートメントとして記述するべき。
  2. equals()hashCode()の両方をオーバーライドすること。
  3. パラメータobjに値が代入されない。これはfinalとして宣言できる。

 3つのエラーのうち2つはCheckstyleでも検出されたものです。PMDだけが指摘した問題点は、一般的なJavaコーディングのベストプラクティスである「returnステートメントの数をなるべく減らしてコードをシンプルにする」に由来するものです。ベストプラクティスを重視するPMDの姿勢がよくわかります。PMDは、Javadocコメントの欠落を意に介しませんし、メソッドや変数の命名規則、スペースとインデントの用法、その他の純然たるコーディング標準の問題には注意を向けません。

 さて、FindBugsはどうでしょうか。他のツールと同様に、FindBugsでもこのコードに3つの問題点が指摘されますが、その性質は他の2つのツールで検出されるエラーの性質と異なります。

  1. equals()メソッドは、引数が常に特定のデータ型であることを前提としてはならない。
  2. このクラスは、equals()を定義し、Object.hashCode()を使用している。
  3. equals()メソッドがnull引数をチェックしない。

 最初のエラーは、このequals()メソッドのデザインにある大きな欠陥の1つです。引数がDogクラスのインスタンスでない場合、メソッドは正常に動作しません。この問題に関する詳細な情報も以下のようにFindBugsから提供され、問題と解決方法を知るのに役立ちます。

equals(Object o)メソッドはoが常に特定のデータ型であることを前提としてはなりません。oがthisと異なる型の場合はfalseを返す必要があります。

 2番目の問題点は、他の2つのツールが指摘したequals()/hashCode()の問題と同じです。ただし、FindBugsが提示する説明は、この問題の原因と解決方法にも言及します。

このクラスはequals(Object)をオーバーライドしますが、hashCode()をオーバーライドしません。hashCode()の実装はjava.lang.Objectから継承されますが、この実装はVMからオブジェクトに割り当てられる任意の値をIDハッシュコードとして返します。したがって、このクラスは、同一のオブジェクトは同一のハッシュコードを持たなくてはならないという原則に反する可能性が非常に高くなります。このクラスのインスタンスがHashMap/HashTableに挿入されることはないと考えている場合は、以下のhashCode実装の使用を推奨します。
public int hashCode() {
    assert false : "hashCode not designed";
    return 42; // 定数は任意の値でかまわない
}

 3番目の問題点は、FindBugsだけが検出する別の潜在的なバグです。このメソッドでは、パラメータのnull値がチェックされません。FindBugsでは、エラーメッセージに示される情報よりもさらに正確にこの問題が分析され、問題の詳細と簡単な解決方法を示す説明が提示されます。

equals(Object)の実装は、引数として渡されるnullをチェックしないため、java.lang.Object.equals()で定義された規約に違反します。すべてのequals()メソッドはnullが渡された場合にfalseを返すべきです。

 この簡単な例からも、FindBugsと他の静的分析ツールに次の2つの大きな相違があることがわかります。

  • FindBugsは、アプリケーションのバグを招くコーディングの問題にほぼ全面的に重点を置く。
  • FindBugsは、検出した問題について明確かつ詳細な説明を提示する。これは問題の理解と修正に役立つ。

 別の例をもう1つ見てみましょう。

public void foo() {
   List<String> values = null;
   List<Integer> results = new ArrayList<Integer>();
   for (String str : values) {
       int value = Integer.parseInt(str);
     results.add(new Integer(value));
   }
}

 この例について、Checkstyleは、Javadocコメントの欠落以外に大きな問題点を検出しません。Checkstyleを念入りに設定した場合を除き、'<' '>'演算子の前後にスペースがないことが指摘されます。ここでもCheckstyleはコーディングのスタイルとレイアウトにこだわっています。

 デフォルト設定のPMDは、6つの問題点を検出します。ほとんどは、パフォーマンスの最適化に関するものです。以下に一部を示します。

  1. 変数valuesresultsvalueはfinalとして宣言できる(理屈の上では正しい指摘ですが、実際にはまず見られないコーディングスタイルです)。
  2. ループ内で変数をインスタンス化することは避ける(これは妥当な指摘です)。
  3. 変数resultsに'DU'アノマリーがある(ややあいまいな指摘です)。

 一方、FindBugsは2つの問題点しか指摘しません。

  1. values変数にnullポインタ逆参照がある。これは実行時にNullPointerExceptionを招く(図4を参照)。
  2. new Integer(value)の使い方が非効率。代わりにInteger.valueOf(value)を使用すると、不要なオブジェクトの作成(PMDで検出される2番目の問題と同様の潜在的なパフォーマンスの問題)が避けられる。
図4. FindBugs分析の実行例: 'values'変数のnullポインタ逆参照によってNullPointerExceptionが実行時に生成される。
図4. FindBugs分析の実行例: 'values'変数のnullポインタ逆参照によってNullPointerExceptionが実行時に生成される。

 ここでもやはり、FindBugsが潜在的なバグに重点を置いていることがわかります。

 静的分析ツールに誤報は付き物です。しかし、FindBugsで検出される問題点は高い確率で(個人的な経験から言って50%以上)本物のバグを明らかにします。

次のページ
FindBugsをビルドプロセスに統合する

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

John Ferguson Smart(John Ferguson Smart)

政府機関や企業の国内外の開発チームから成る大規模なJ2EEプロジェクトに数多く携わる。J2EEのアーキテクチャと開発、およびITプロジェクト管理を専門とする。オープンソースのJavaテクノロジの経験も豊富。自らのテクニカルブログを公開中(www.jroller.com/page/wakaleo)。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/3024 2008/10/02 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング