どこからも使用されてないクラスを列挙する

これはJava Advent Calendarの2日目の記事です。

さて、Javaで開発をしているといつの間にかどこからも使用されていないクラスというものが出てきてしまいます。
リファクタリングや仕様変更の結果、呼び出されてなくなったクラスです。

それら、どこからも使用されてないクラスを一覧化してみます。

まず、使用されているクラスを一覧化します。
一覧化にはjdepsを使用します。

jdepsの概要についてはCLOVERの記事を参照してください。

CLOVERの記事ではclassもしくはjarを指定していますが、実はパッケージ(フォルダ)を指定することで、その下のクラスの依存先をすべて出力してくれます。

使用されているクラスの一覧が出来たら、全体のクラスの一覧から引き算してあげます。
package 配下のクラス一覧を取得する方法はいろいろあるようですが、今回はGuavaのClassPathを使用しました。

ということで、以下のような感じです。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.file.Paths;
import java.util.Set;
import java.util.stream.Collectors;

import com.google.common.reflect.ClassPath;

public class SelectNotUsedClass {

  public static void main(String[] args) throws IOException {

    // buildはコンパイルされたクラスの出力先
    Process process = new ProcessBuilder("jdeps", "-v", Paths.get("build").toAbsolutePath().toString()).start();

    //Charsetは各環境に合わせてください 
    Set<String> dependencies = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("Windows-31J"))).lines().map(s -> s.trim())
        .filter(s -> s.contains("->")).map(s -> s.substring(s.indexOf("->"))).map(s -> s.replace("->", "").trim().replaceAll("\\s+.*$", ""))
        .collect(Collectors.toSet());

    ClassLoader loader = Thread.currentThread().getContextClassLoader();

    // jp.hoge.xxxxは依存関係を調べたいパッケージの名前
    ClassPath.from(loader).getTopLevelClassesRecursive("jp.hoge.xxxx").stream().map(info -> info.getName()).filter(c -> !dependencies.contains(c))
        .forEach(System.out::println);
  }
}

jdepsが出るまでは.javaファイルのimport行を解析して出してたりしましたが、ずいぶんと楽になりましたね。

      • -

ブコメ
> これ普通に便利そう。ちょっと気になったんだけどテストからは参照されてるけどプロダクトコードからは一切参照されてないのって検出できるのかかしら?

というのがありましたが、
テストクラスとプロダクトコードは別管理されているはずなので、プロダクトコードのみをコンパイルした上で(テストコードは取り除いて)実行すればテストからのみ参照されているものも取り除くことが出来ます。
その後、いくつかのテストコードがコンパイルエラーになるので、そちらは個別で削除してください。