概要

AntからPMDを実行します。

サンプルターゲット

  • build.xmlに記述するターゲットサンプル
    <target name="pmd">
      <taskdef name="pmd" classname="net.sourceforge.pmd.ant.PMDTask" />
      <mkdir dir="${build.reports}" />
      <pmd rulesetfiles="ruleset.xml" encoding="${compile.encoding}"
           cacheLocation="${build.reports}/pmd/pmd.cache">
        <sourceLanguage name="java" version="${compile.source}"/>
        <classpath refid="project.class.path" />
        <formatter type="xml" toFile="${build.reports}/pmd.xml" />
        <formatter type="text" toConsole="true" />
        <fileset dir="${src.dir}" excludes="**/module-info.java" includes="**/*.java" />
      </pmd>
    </target>
    
  • PMD 6.0.0向けのruleset.xmlサンプル
    <?xml version="1.0"?>
    <ruleset name="Custom ruleset"
        xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
      <description>This ruleset checks my code for bad stuff</description>
      <rule ref="category/java/bestpractices.xml">
        <exclude name="AvoidPrintStackTrace" />
        <exclude name="SystemPrintln" />
      </rule>
      <rule ref="category/java/codestyle.xml">
        <exclude name="AtLeastOneConstructor" />
        <exclude name="OnlyOneReturn" />
        <exclude name="ShortVariable" />
        <exclude name="LongVariable" />
        <exclude name="LocalVariableCouldBeFinal" />
        <exclude name="MethodArgumentCouldBeFinal" />
      </rule>
      <rule ref="category/java/design.xml">
        <exclude name="DataClass" />
        <exclude name="LawOfDemeter" />
        <exclude name="LoosePackageCoupling" />
        <exclude name="NcssCount" />
      </rule>
      <!-- rule ref="category/java/documentation.xml" / -->
      <rule ref="category/java/errorprone.xml">
        <exclude name="BeanMembersShouldSerialize" />
        <exclude name="DataflowAnomalyAnalysis" />
        <exclude name="DoNotCallSystemExit" />
        <exclude name="NullAssignment" />
        <exclude name="UseProperClassLoader" />
      </rule>
      <rule ref="category/java/multithreading.xml">
        <exclude name="DoNotUseThreads" />
      </rule>
      <rule ref="category/java/performance.xml">
        <exclude name="AvoidInstantiatingObjectsInLoops" />
      </rule>
    </ruleset>
    

解説

  1. PMDからダウンロードしたpmd-bin-x.x.ziplib以下にあるpmd-x.x.jarなどのjarファイルを%ANT_HOME%\libにコピー、もしくは環境変数%PMD_HOME%を設定した場所にlibを展開
  2. 上記のようなpmdターゲットをbuild.xmlに追加し、ant pmdpmd.xmlを生成、jenkinsPMDプラグインなどで読み込む
  3. チェックするルールをカスタマイズする場合は、ruleset.xmlを記述してこれをrulesetfiles属性で参照する

toFile or toConsole

  • FindBugsのように、xmlファイルとコンソールの両方に結果を表示する場合は、formatter要素のtoFiletoConsole属性を併用する
    • ant -v pmdは、出力される情報が多すぎる
  • PMD - Ant Task にあるように、<formatter type="html" toFile="pmd_report.html" toConsole="true"/>や、<formatter type="xml" toFile="${build.reports}/pmd.xml" toConsole="true" />toFiletoConsoleを指定しても、ファイルにのみ結果が書き出されて、コンソールには何も出力されない
  • <formatter type="text" toConsole="true" />と、toFile属性なしでtoConsole="true"とする必要がある?
    • <formatter type="text" toConsole="false" />は、toFile or toConsole needs to be specified in Formatterとなって、BUILD FAILED
  • ファイルとコンソールの両方に出力する場合は、formatterを複数指定しなくてはならない?

更新履歴

PMD 6.5.0

> pmd.bat -dir C:\tmp -R category/java/codestyle.xml/UnnecessaryFullyQualifiedName
C:\tmp\Test.java:9: Unnecessary use of fully qualified name 'Arrays.stream' due to existing import 'java.util.stream.*'
import java.awt.Component;
import java.awt.Container;
import java.util.Arrays;
import java.util.stream.*;
// import java.util.stream.Stream; // *を使用しない場合は誤検出は発生しない

public final class Test {
  public static Stream<Component> stream(Container parent) {
    return Arrays.stream(parent.getComponents())
      .filter(Container.class::isInstance)
      .map(c -> stream(Container.class.cast(c)))
      .reduce(Stream.of(parent), Stream::concat);
  }
}

PMD 6.0.0

PMD 5.1.3

  • 上記の誤検出はpmd-bin-5.1.3-SNAPSHOT.zipですべて修正済み。ただし新規で今まで問題なかった以下のようなコードのmakeButton(Action action, String title)UnusedPrivateMethodとなってしまう 修正済み
C:\temp>C:\pmd-bin-5.1.3-SNAPSHOT\bin\pmd.bat -dir . -rulesets java-unusedcode
C:\temp\MenuBarFactoryTest.java:44: Avoid unused private methods such as 'makeButton(Action,String)'.
import java.awt.*;
import java.util.Arrays;
import java.util.List;
import javax.swing.*;
import javax.swing.text.DefaultEditorKit;

public final class MenuBarFactoryTest {
  public static JMenuBar makeManuBar() {
    JMenuBar mb = new JMenuBar();
    mb.add(makeFileMenu());
    return mb;
  }
  private static JMenu makeFileMenu() {
    JMenu menu = new JMenu("File");
    menu.add(makeEditMenuItem(makeEditButtonBar(Arrays.asList(
        makeButton(new DefaultEditorKit.CutAction(),   "Cut"),
        makeButton(new DefaultEditorKit.CopyAction(),  "Copy"),
        makeButton(new DefaultEditorKit.PasteAction(), "Paste")))));
    return menu;
  }
  private static JMenuItem makeEditMenuItem(final JComponent edit) {
    JMenuItem item = new JMenuItem("Edit") {
      @Override public Dimension getPreferredSize() {
        Dimension d = super.getPreferredSize();
        d.width += edit.getPreferredSize().width;
        d.height = Math.max(edit.getPreferredSize().height, d.height);
        return d;
      }
    };
    item.setEnabled(false);
    item.setLayout(new GridBagLayout());
    item.add(edit);
    return item;
  }
  private static JComponent makeEditButtonBar(List<AbstractButton> list) {
    JPanel p = new JPanel(new GridLayout(1, list.size(), 0, 0));
    for (AbstractButton b : list) {
      p.add(b);
    }
    p.setBorder(BorderFactory.createEmptyBorder(4, 10, 4, 10));
    p.setOpaque(false);
    return p;
  }
  private static AbstractButton makeButton(Action action, String title) {
    JButton b = new JButton(action);
    b.setText(title);
    return b;
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        createAndShowGUI();
      }
    });
  }
  public static void createAndShowGUI() {
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.setJMenuBar(makeManuBar());
    f.setSize(320, 240);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}

PMD 5.1.2

import java.awt.*;
import javax.swing.*;

public class Foo {
    public JComponent makeUI() {
        Box box = Box.createVerticalBox();
        JTextField field = new JTextField();
        box.add(makePanel("aaa", field));
        return box;
    }
    private static JPanel makePanel(String title, JComponent c) {
        JPanel p = new JPanel(new BorderLayout());
        p.setBorder(BorderFactory.createTitledBorder(title));
        p.add(c);
        return p;
    }
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override public void run() {
                createAndShowGUI();
            }
        });
    }
    public static void createAndShowGUI() {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(new Foo().makeUI());
        f.setSize(320, 240);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

PMD 5.0.0

  • RuleSet short names now require a language prefix, 'basic' is now 'java-basic', and 'rulesets/basic.xml' is now 'rulesets/java/basic.xml'
    • 上記のサンプルで、rulesetfiles属性のbasicなどを、java-basicに変更
  • Removed -targetjdk use -version {name} {version} instead
    • 上記のサンプルから、targetjdk="${compile.source}"を削除

警告

GodClass

  • Ruleset: Design
    /**
      * Very high threshold for WMC (Weighted Method Count). See: Lanza.
      * Object-Oriented Metrics in Practice. Page 16.
      */
    private static final int WMC_VERY_HIGH = 47;
    
    /**
     * Few means between 2 and 5. See: Lanza. Object-Oriented Metrics in
     * Practice. Page 18.
     */
    private static final int FEW_THRESHOLD = 5;
    
    /**
     * One third is a low value. See: Lanza. Object-Oriented Metrics in
     * Practice. Page 17.
     */
    private static final double ONE_THIRD_THRESHOLD = 1.0 / 3.0; //0.33333
    
    //...
    if (wmcCounter >= WMC_VERY_HIGH && atfdCounter > FEW_THRESHOLD && tcc < ONE_THIRD_THRESHOLD) {
    //...
    

SimplifiedTernary

  • PMD Java – Basic
    public class SimplifiedTernaryTest {
      public static void main(String... args) {
        //boolean condition = true;
        boolean b1 = condition ? true  : something(); // can be as simple as condition || something();
        boolean b2 = condition ? false : something(); // can be as simple as !condition && something();
        boolean b3 = condition ? something() : true;  // can be as simple as !condition || something();
        boolean b4 = condition ? something() : false; // can be as simple as condition && something();
      }
      private static boolean something() {
        return true;
      }
    }
    
  • 例:
    model2 = new BasicDirectoryModel(getFileChooser()) {
      @Override public boolean renameFile(File oldFile, File newFile) {
        //return oldFile.canWrite() ? super.renameFile(oldFile, newFile) : false;
        return oldFile.canWrite() && super.renameFile(oldFile, newFile);
      }
    };
    

Jenkins

参考リンク

コメント