PMD 標準的な ruleset とは何かを調べてみる


PMDルールセットを作成するにあたり、標準的なものを作ってみようと思いました。
あまり個人の思いをぶつけたものを作ると、仮に会社で適用するにあたり話が通らなそうであるので、
Github 上に公開されているものをベースに平均値的なものを作成してみます。

目次

サンプルの抽出

  • Githubstar数が100以上のプロジェクトを対象に抽出します。
    100以上は、対象プロジェクト数が 6177 で、数として勝手にちょうどよさそうに思ったからです。
    ただ、repository search api の仕様上、最大1000件までしか取得できなかったため、
    API のソート項目を変えながら、取得可能だった5419プロジェクトを対象にしました。

  • 上記のリポジトリからファイル名が、以下の名前と一致するものを抽出します。
    (pom.xmlruleset定義が書いてあったりしますが、それは対象外…)

    • pmd.xml
    • pmdrulesets.xml
    • pmdruleset.xml
    • pmd-ruleset.xml
    • pmd-rulesets.xml

集計方法

  • ルール単位に集計します。exclude定義があるものはカウント対象外にします。 以下の場合は、rulesets/imports.xml 配下のルールはカウントするけれど、
    TooManyStaticImports は対象外にします。
  <rule ref="rulesets/imports.xml">
    <exclude name="TooManyStaticImports" />
  </rule>
  • ルールの全量は、PMD - PMD Rulesets index にのっています。
    (pmd-4.3.0で、pmd5 のルールセットではないですが大方あっているだろう。で上記から取得しました。)

集計に使用するGithub API

集計のため以下の、API を使用しました。
1. Githubから、リポジトリ検索 API でスター数 100 以上の Javaプロジェクトを検索
2. コード検索 APIで 1. で取得したリポジトリから PMD の設定ファイルを検索
3. 2. でヒットした PMD の設定ファイルを解析。使用ルールを集計

リポジトリ検索 API 使用 URL

  • star が200以上の java プロジェクトを検索する
  https://api.github.com/search/repositories?q=language:java+stars:>=200
  • star が100以上、199以下の java プロジェクトを検索する
        https://api.github.com/search/repositories?q=language:java+stars:100..199
    

コード検索 API URL

  • repository から pmd の設定ファイルを検索する。
https://api.github.com/search/code?q=filename:pmd-rulesets.xml+filename:pmd.xml+filename:pmdruleset.xml+filename:pmdrulesets.xml+filename:pmd-ruleset.xml+repo:blueshen/ut-maven-plugin

PMD 設定ファイルの取得 ダウンロード URL

ファイルの実体は、https://raw.githubusercontent.com 配下に存在します。
コード検索 API の戻り に含まれるhtml_url を以下のように置換することで、 実ファイルの URL を取得することができます。
python で 記述したところ以下のようになりました。

    # convert row url
    row_url = doc.get('html_url')
    # URLを https://github.com から、https://raw.githubusercontent.comに置換
    row_url = row_url.replace('https://github.com', 'https://raw.githubusercontent.com')
    # ディレクトリ blob/ を削除
    row_url = row_url.replace('blob/', '')

変換後の URL は以下になります。
この URL にアクセスすると、実ファイルが取得できます。

https://raw.githubusercontent.com/blueshen/ut-maven-plugin/dc371d859292cdad07cc35a31bc9403e0384f005/pmd-rulesets.xml

実装

実装は python で行いました。
python のライブラリを検索したところ、github3.py 等幾つかありました。
しかし、何故か pip は上手くいったにも関わらず import エラーになったりしたため、
requestjson を使って自前で実装しました。


集計結果

1. PMDルールファイルを保持しているプロジェクト数

  • 81/5419 プロジェクト
    個人的に10%くらいは、使っているのかと思ってましたが、
    1.5パーセント程度でほとんど使われていないようです。
    取得の仕方が悪かったのかもしれません。集計サンプル数として少ないかもですが、
    とりあえず続けます。

2. 適用されているルールのTOP10

NOルール名カウント
1rulesets/java/basic.xml/BooleanInstantiation42
1rulesets/java/basic.xml/UnconditionalIfStatement42
3rulesets/java/strings.xml/UnnecessaryCaseChang41
3rulesets/java/strings.xml/StringToString41
3rulesets/java/strings.xml/StringInstantiatio41
6rulesets/java/strings.xml/InefficientStringBuffering40
6rulesets/java/basic.xml/CollapsibleIfStatement40
6rulesets/java/strings.xml/UseStringBufferLengt40
6rulesets/java/strings.xml/UseIndexOfChar40
6rulesets/java/strings.xml/StringBufferInstantiationWithChar40
6rulesets/java/basic.xml/DoubleCheckedLocking40
6rulesets/java/strings.xml/UselessStringValueOf40

basic strings ルールセットが多く使われているようです。

3. 適用除外ルールのTOP10

NOルール名カウント
1rulesets/java/naming.xml/LongVariable-24
2rulesets/java/naming.xml/ShortVariable-23
3rulesets/java/naming.xml/AbstractNaming-20
4rulesets/java/imports.xml/TooManyStaticImports-18
5rulesets/java/naming.xml/ShortMethodName-16
6rulesets/java/naming.xml/VariableNamingConventions-14
6rulesets/java/naming.xml/ShortClassName-14
8rulesets/java/optimizations.xml/AvoidInstantiatingObjectsInLoops-11
9rulesets/java/coupling.xml/LawOfDemeter-10
10rulesets/java/optimizations.xml/LocalVariableCouldBeFinal-8
10rulesets/java/controversial.xml/OnlyOneReturn-8
10rulesets/java/controversial.xml/DataflowAnomalyAnalysis-8
10rulesets/java/strings.xml/AvoidDuplicateLiterals-8
10rulesets/java/controversial.xml/AtLeastOneConstructor-8

Naming ルールセット内のルールが除外指定が多く除外指定されています。
確かに準拠するのが難しい気がします。

4. 詳細

集計データの詳細( MongoDBCSVエクスポートデータ )は、
こちらのGoogle スプレッドシート
を添付しました。
* 集計サマリ
* 除外集計サマリ
を見てもらえれば良いかと思います。


PMDルールファイル

集計結果を元に以下のロジックでルールファイルを作成します。

  1. ルールの集計数が平均値を超えるものをルール単位で、記述。
  2. 1.に含まれる場合でも、除外数が平均値を超えるものは除外。

作成したルールファイルは以下になります。

<ruleset name="Custom ruleset"
         xmlns="http://pmd.sf.net/ruleset/1.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
         xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">

    <description>
        Github Collect Rule Set
    </description>

    <rule ref="rulesets/java/basic.xml/BooleanInstantiation"/>
    <rule ref="rulesets/java/basic.xml/UnconditionalIfStatement"/>
    <rule ref="rulesets/java/strings.xml/UnnecessaryCaseChange"/>
    <rule ref="rulesets/java/strings.xml/StringToString"/>
    <rule ref="rulesets/java/strings.xml/StringInstantiation"/>
    <rule ref="rulesets/java/strings.xml/InefficientStringBuffering"/>
    <rule ref="rulesets/java/basic.xml/CollapsibleIfStatements"/>
    <rule ref="rulesets/java/strings.xml/UseStringBufferLength"/>
    <rule ref="rulesets/java/strings.xml/UseIndexOfChar"/>
    <rule ref="rulesets/java/strings.xml/StringBufferInstantiationWithChar"/>
    <rule ref="rulesets/java/basic.xml/DoubleCheckedLocking"/>
    <rule ref="rulesets/java/strings.xml/UselessStringValueOf"/>
    <rule ref="rulesets/java/basic.xml/BrokenNullCheck"/>
    <rule ref="rulesets/java/basic.xml/ClassCastExceptionWithToArray"/>
    <rule ref="rulesets/java/basic.xml/AvoidDecimalLiteralsInBigDecimalConstructor"/>
    <rule ref="rulesets/java/imports.xml/DontImportJavaLang"/>
    <rule ref="rulesets/java/basic.xml/BigIntegerInstantiation"/>
    <rule ref="rulesets/java/logging-java.xml/AvoidPrintStackTrace"/>
    <rule ref="rulesets/java/basic.xml/ForLoopShouldBeWhileLoop"/>
    <rule ref="rulesets/java/basic.xml/ReturnFromFinallyBlock"/>
    <rule ref="rulesets/java/imports.xml/DuplicateImports"/>
    <rule ref="rulesets/java/basic.xml/JumbledIncrementer"/>
    <rule ref="rulesets/java/logging-java.xml/SystemPrintln"/>
    <rule ref="rulesets/java/strings.xml/UseEqualsToCompareStrings"/>
    <rule ref="rulesets/java/braces.xml/IfElseStmtsMustUseBraces"/>
    <rule ref="rulesets/java/braces.xml/WhileLoopsMustUseBraces"/>
    <rule ref="rulesets/java/strings.xml/AppendCharacterWithChar"/>
    <rule ref="rulesets/java/braces.xml/ForLoopsMustUseBraces"/>
    <rule ref="rulesets/java/strings.xml/InefficientEmptyStringCheck"/>
    <rule ref="rulesets/java/clone.xml/CloneThrowsCloneNotSupportedException"/>
    <rule ref="rulesets/java/strings.xml/AvoidStringBufferField"/>
    <rule ref="rulesets/java/basic.xml/OverrideBothEqualsAndHashcode"/>
    <rule ref="rulesets/java/basic.xml/MisplacedNullCheck"/>
    <rule ref="rulesets/java/finalizers.xml/AvoidCallingFinalize"/>
    <rule ref="rulesets/java/basic.xml/AvoidUsingOctalValues"/>
    <rule ref="rulesets/java/basic.xml/CheckResultSet"/>
    <rule ref="rulesets/java/naming.xml/ClassNamingConventions"/>
    <rule ref="rulesets/java/basic.xml/AvoidThreadGroup"/>
    <rule ref="rulesets/java/strings.xml/ConsecutiveLiteralAppends"/>
    <rule ref="rulesets/java/basic.xml/AvoidMultipleUnaryOperators"/>
    <rule ref="rulesets/java/strings.xml/InsufficientStringBufferDeclaration"/>
    <rule ref="rulesets/java/logging-java.xml/LoggerIsNotStaticFinal"/>
    <rule ref="rulesets/java/naming.xml/SuspiciousConstantFieldName"/>
    <rule ref="rulesets/java/naming.xml/SuspiciousHashcodeMethodName"/>
    <rule ref="rulesets/java/strings.xml/ConsecutiveAppendsShouldReuse"/>
    <rule ref="rulesets/java/finalizers.xml/FinalizeDoesNotCallSuperFinalize"/>
    <rule ref="rulesets/java/naming.xml/AvoidDollarSigns"/>
    <rule ref="rulesets/java/imports.xml/ImportFromSamePackage"/>
    <rule ref="rulesets/java/naming.xml/SuspiciousEqualsMethodName"/>
    <rule ref="rulesets/java/basic.xml/DontCallThreadRun"/>
    <rule ref="rulesets/java/basic.xml/DontUseFloatTypeForLoopIndices"/>
    <rule ref="rulesets/java/finalizers.xml/FinalizeOverloaded"/>
    <rule ref="rulesets/java/finalizers.xml/EmptyFinalizer"/>
    <rule ref="rulesets/java/naming.xml/MethodWithSameNameAsEnclosingClass"/>
    <rule ref="rulesets/java/basic.xml/CheckSkipResult"/>
    <rule ref="rulesets/java/basic.xml/ExtendsObject"/>
    <rule ref="rulesets/java/imports.xml/UnusedImports"/>
    <rule ref="rulesets/java/logging-java.xml/MoreThanOneLogger"/>
    <rule ref="rulesets/java/basic.xml/AvoidUsingHardCodedIP"/>
    <rule ref="rulesets/java/basic.xml/AvoidBranchingStatementAsLastInLoop"/>
    <rule ref="rulesets/java/imports.xml/UnnecessaryFullyQualifiedName"/>
    <rule ref="rulesets/java/clone.xml/CloneMethodMustImplementCloneable"/>
    <rule ref="rulesets/java/empty.xml/EmptyCatchBlock"/>
    <rule ref="rulesets/java/clone.xml/ProperCloneImplementation"/>
    <rule ref="rulesets/java/naming.xml/NoPackage"/>
    <rule ref="rulesets/java/naming.xml/PackageCase"/>
    <rule ref="rulesets/java/naming.xml/MethodNamingConventions"/>
    <rule ref="rulesets/java/naming.xml/MisleadingVariableName"/>
    <rule ref="rulesets/java/finalizers.xml/FinalizeShouldBeProtected"/>
    <rule ref="rulesets/java/finalizers.xml/FinalizeOnlyCallsSuperFinalize"/>
    <rule ref="rulesets/java/naming.xml/BooleanGetMethodName"/>
    <rule ref="rulesets/java/naming.xml/GenericsNaming"/>
    <rule ref="rulesets/java/unusedcode.xml/UnusedLocalVariable"/>
    <rule ref="rulesets/java/unusedcode.xml/UnusedPrivateField"/>
    <rule ref="rulesets/java/design.xml/SimplifyConditional"/>
    <rule ref="rulesets/java/design.xml/IdempotentOperations"/>
    <rule ref="rulesets/java/unusedcode.xml/UnusedPrivateMethod"/>
    <rule ref="rulesets/java/design.xml/UnnecessaryLocalBeforeReturn"/>
    <rule ref="rulesets/java/unusedcode.xml/UnusedFormalParameter"/>
    <rule ref="rulesets/java/design.xml/ConstructorCallsOverridableMethod"/>
    <rule ref="rulesets/java/design.xml/InstantiationToGetClass"/>
    <rule ref="rulesets/java/design.xml/PreserveStackTrace"/>
    <rule ref="rulesets/java/design.xml/CloseResource"/>
    <rule ref="rulesets/java/design.xml/EqualsNull"/>
    <rule ref="rulesets/java/design.xml/AvoidInstanceofChecksInCatchClause"/>
    <rule ref="rulesets/java/design.xml/OptimizableToArrayCall"/>
    <rule ref="rulesets/java/design.xml/SimplifyBooleanReturns"/>
    <rule ref="rulesets/java/design.xml/MissingStaticMethodInNonInstantiatableClass"/>
    <rule ref="rulesets/java/design.xml/CompareObjectsWithEquals"/>
    <rule ref="rulesets/java/strictexception.xml/ExceptionAsFlowControl"/>
    <rule ref="rulesets/java/optimizations.xml/UseArrayListInsteadOfVector"/>
    <rule ref="rulesets/java/android.xml/DoNotHardCodeSDCard"/>
    <rule ref="rulesets/java/design.xml/AvoidProtectedFieldInFinalClass"/>
    <rule ref="rulesets/java/unusedcode.xml/UnusedModifier"/>
    <rule ref="rulesets/java/optimizations.xml/UseArraysAsList"/>
    <rule ref="rulesets/java/optimizations.xml/AvoidArrayLoops"/>
    <rule ref="rulesets/java/design.xml/FinalFieldCouldBeStatic"/>
    <rule ref="rulesets/java/design.xml/SimplifyBooleanExpressions"/>
    <rule ref="rulesets/java/android.xml/CallSuperFirst"/>
    <rule ref="rulesets/java/android.xml/CallSuperLast"/>
    <rule ref="rulesets/java/strictexception.xml/AvoidRethrowingException"/>
    <rule ref="rulesets/java/design.xml/NonThreadSafeSingleton"/>
    <rule ref="rulesets/java/design.xml/PositionLiteralsFirstInComparisons"/>
    <rule ref="rulesets/java/design.xml/SingularField"/>
</ruleset>

Githubから収集したデータを元に、PMDルールファイルを作成しました。
何より、使用しているプロジェクトが少なかったことにびっくりしましたが、
適用ルール、適用除外ルールには、一定の傾向があるような集計結果になりました。
XMLの出力までを自動化もできそうなので、
定期的に結果を収集して、ルールファイルを作成してもいいかもしれません。

以上です。

コメント