重複コード撲滅に役立つIntellij IDEAの機能

この記事はJetBrains IDE Advent Calendar 2015の4日目です。

2年ほど前、命名規約やプログラミングの慣習に違反したお行儀のよくないコードをIntellij IDEAを使って見つけて、改善していくことを書きました。「publicなメソッドの名前がget〜なのに戻り値がvoidで、どうやらgetしてきた何かをフィールドにsetしてて、別なアクセサで取得するらしい…」みたいなアレです。

そういった行儀の悪さも困りものなのですが、既存のコードベースの上に機能追加したり不具合修正したりする上でもう1つ厄介なのは、コードの重複です。

  • 大人数が横串を通す時間もなく作ったので、各人が同じものをあちこちで実装してしまう
  • コードベースが数十万行などと巨大なので、既に誰かが実装済みなことを知らずに実装してしまう
  • 改修するとテストが大変なので、コピーしてちょっとだけ挙動を変えた実装を作ってしまう

私の職場では、そんな感じでコードの重複が積み重なってきたようです。すると、ある不具合を修正したつもりでも、他のところにコピーされていて直しきれなかったりするので、なかなか骨が折れます(つらい)。

最近IDEAを職場で使い始め、そういう重複コードを少しつづ潰せて便利でしたので、カンタンにご紹介します。

Inspection

まずはIDEAに備わった強力なコード静的解析(Inspection)を使います。IDEAの「Preference(設定パネル)>Editor>Inspections」内をduplicate(重複)などと検索して、設定を見つけてONにしてみてください。

重複に関するInspectionは、開いているファイルに対してはその場で実行してくれます。またAnalyze>Inspectionから、プロジェクト全体など指定したスコープで一括で実行することもできます。

Javaでの重複に関するInspectionをいくつか紹介してみます(他にJS、Maven、Java EEなどなど様々なコードの重複を見つけるInspectionも備わっています)。

Control flow issues > Duplicate condition in ‘if’ statement

if/else文で異なる分岐に同じ条件が繰り返ししているのを見つけてくれます。例えば以下の例でいうと、isA(booleanの変数だったり、booleanに評価される式)が重複としてハイライトされます。

if (isA || isB) {
  ...
} else if (isA || isC) {
  ...
}

Control flow issues > Duplicate condition on ‘&&’ or ‘||’

&&や||で同じ式が繰り返されているとハイライトされます。以下の例でいうとisAです。

if (isA || isB || isC || isD||isA) {
  ...
}

if (isA && isB || isC || isA && isD) {
  ...
}

isAが3文字のboolean変数だと目視でも重複に気づけるかもしれません。しかし、これがもし複雑な式だと、見落としてしまいそうです(実際にお仕事で見つかったときは驚きました)。

Internationalization issues > Duplicate String Literal

Stringリテラルの重複を見つけてくれます。「なんとか区分」「なんとかコード」みたいな定数が重複しまくっていると大変ですよね。そういうときに役立ちます。

上の例で言うとCONSTANT_A、OTHER_CONST、keyが重複したリテラルです。

class Foo {
    private static final String CONSTANT_A = "ABC1234";
    void bar() {...}
}

class Hoge {
    private static final String OTHER_CONSTANT = "ABC1234";
    void fuga() {
        String key = "ABC1234";    
        ...
    }
}

IDEAでは、これら見つかった重複に対し、

  • どれか1つを残して、他から参照するようにする
  • 指定したクラスに定数を新設して、それを参照にする

といったQuick Fixをかけることができて、あっという間に重複を除去できて、大変便利です(何百も重複しているリテラルを見つけたときは気が滅入りかけましたが、IDEAに救われました)。

Locate Duplicate

Locate Duplicateはさらに強力で、その名の通り、複数ステップに及ぶ似たようなコードを見つけてくれます。

ステップに登場してくるローカル変数、フィールド、メソッド、クラス名、リテラルなどを匿名化(型があっていれば、別な名前でも重複と推測する)して、「ちょっと違うけど、この辺が重複してそうですよ」ということまで調べてくれます。

例えば次の例では、2つのメソッドmatrixinizeの5行とtablenizeの5行それぞれは、変数名やリテラルなどが微妙に異なっていますが、重複としてハイライトされます。こうして見つけた重複から共通部分を抽出し、それを使うように修正できるでしょう。

public static List<List<String>> matrixinize(String e, int count) {
    System.out.println("matrixnize begin");
    List<String> row = Collections.nCopies(count, e);
    List<List<String>> matrix = Collections.nCopies(count, row);
    System.out.println("matrixnize end");
    return matrix;
}

public static List<List<String>> tablenize(String e, int count) {
    System.out.println("tablenize begin");
    List<String> row = Collections.nCopies(count, e);
    List<List<String>> table = Collections.nCopies(count, row);
    System.out.println("tablenize end");
    return table;
}

つい先日リリースされたばかりのIDEA 15からは、Locate Duplicatesも開いているファイルに対しては、その場で実行してくれるようになりました。14まではいちいち、メニューのAnalyze>Locate Duplicateからたどって設定パネルを操作してようやく動作するので少し手間でしたが、裏でよしなにやってくれるようになって、非常に便利です!! ただ、あまりに重複だらけのコードを相手にしていると、若干うざいです(つらい)。

見つけた重複は、深刻さにもよりますが、カンタンなものはIDEAの様々なリファクタリング(メソッドの抽出とか、メソッドオブジェクトの抽出とか)でサクサク潰しています。

おしまい

こうしたIDEAの機能を活用し、がんばってダメなコードや重複を減らして、後任者に恨まれないようにしていきたいです。

人気の投稿