Skip to content
Rafael de Paula edited this page Jun 1, 2023 · 1 revision

O que é o PMD?

O PMD é um analisador estático de código criado no começo dos anos 2000. Ele analisa o código para checar boas práticas e design de código por meio de regras (“rules”). Útil para deixar o código como um todo com o mesmo padrão e evitar erros no código. Ele fornece suporte para muitas linguagens de programação, sendo a Java uma das mais conhecidas.

Versão

No momento da escrita está sendo usada a versão 6.55.0 para o Java 17. A versão 7.0.0 está quase sendo lançada, trazendo algumas incompatibilidades, como sintaxe de XPath e versão da ferramenta “Designer”. Essa Wikipage refere-se ao uso específico com a linguagem Java e o gerenciador de pacotes Gradle.

Configuração Gradle

O PMD funciona como uma “task” do Gradlew. As tasks dele são o pmdMain, pmdTest e pmdSourceSet. A primeira analisa os .java de produção, o segundo os de teste e o terceiro é para outros códigos fonte. As tasks são acionadas por meio do comando ./gradlew check ou ./gradlew ch. Importante observar que estes comandos também acionam outros analisadores, como o Spotbugs e o CheckStyle. Configurando o PMD no arquivo build.gradle.

1 - Registra o plugin.

plugins {
    // [... outras declarações]
    id 'pmd'
}

2 - Acrescenta as configurações.

pmd {
    sourceSets = [ project.sourceSets.main ] // definem os arquivos dos códigos fonte que serão analisados.
    consoleOutput = true                     // mostra no console o resultado
    toolVersion = "6.55.0"                   // versão do PMD
    ruleSets = []                            // reseta o conjunto de regras para não conflitar com arquivos de regras, se estes existirem
    ruleSetFiles = rootProject.files("config/pmd/ruleset.xml") // path do arquivo de ruleset

    pmdMain { // opcional, usada para definir exclusões no build.gradle
        excludes = [
                '**/Application.*',
        ]
    }
}

obs: é possível declarar o ruleSets dessa forma, ruleSets = ["category/java/bestpractices.xml"]

obs 2: Ver documentação do plugin para mais opções de configuração

Rule (regra)

Regras são checagens (“checks”) do código, possuem nome, versão da linguagem alvo, descrição, mensagem, exemplos e propriedades (nem todos esses são obrigatórios). Cada regra pertence a um dos 8 tipos de categorias, que auxiliam na semântica da regra. Elas podem ser organizadas em um arquivos chamado de conjunto de regras (“ruleset”). Cada regra é explicada na documentação do PMD. Algumas regras podem ter seus nomes mudados ou podem ter sido deprecadas dependendo da versão do PMD.

Supressão de aviso de rule

Uma das formas de se suprimir o aviso levantado por uma rule é acrescentar a anotação @SuppressWarnings(“PMD”) ou SuppressWarnings("PMD.nome-da-rule") na classe ou método onde o erro ocorre. Essa técnica não funciona em casos em que a rule não está em uma classe ou método (ex: está nos “imports”).

Também é possivel suprimir usando expressões regulares ou notação “XPath”.

<rule ref="rulesets/java/unusedcode.xml/UnusedFormalParameter">
  <properties>
    <property name="violationSuppressXPath" value=".[typeIs('java.lang.String')]"/>
  </properties>
</rule>

A “property” poderia ser trocada por:

<property name="violationSuppressXPath" value="./ancestor::ClassOrInterfaceDeclaration[contains(@Image, 'Bean')]"/>

ou

<property name="violationSuppressXPath" value="./ancestor::ClassOrInterfaceDeclaration[matches(@Image, '^.*Bean$')]"/>

obs: A notação XPath foi criada para ser usada com xml e possui outras aplicações na web em geral. No contexto do PMD, é usada a versão 1.0, com alguma adaptabilidade para a 2.0, até a última versão do PMD 6. A partir do PMD 7.0.0, será usada a versão XPath 3.1. As diferentes versões do XPath não são inteiramente compatíveis, podendo trazer problemas.

obs 2: A notação XPath tem funções como “contains()” e “matches()” que ajudam na seleção de nós. O PMD adicionou algumas outras funções como o “typeIs()”. A documentação lista as funções do XPath do PMD.

Categorias

As categorias que uma regra nativa do PMD são “Best Practices”, “Code Style”, “Design”, “Documentation”, “Error prone”, “Multithreading”, “Performance”, “Security”.

Tag de uma rule

No arquivo de ruleset, as rules podem ser declaradas por categoria,

<rule ref="category/java/bestpractices.xml"/>

individualmente,

<rule ref="category/java/bestpractices.xml/AccessorClassGeneration" />

com propriedades genéricas (ex: “message” e “priority”),

<rule ref="category/java/errorprone.xml/EmptyCatchBlock"
      message="Empty catch blocks should be avoided" >
      <priority>5</priority>
</rule>

além dessas, também existem as propriedades genéricas que utilizam a tag <property> como “legalCollectionTypes” e “ignoredAnnotations”.

<properties>
<property name="legalCollectionTypes"
          		value="java.util.ArrayList|java.util.Vector|java.util.HashMap"/>
</properties>

Rule usando a property genérica ignoredAnnotations para suspender o aviso na anotação da classe que contém a anotação “SpringBootApplication”.

<rule ref="category/java/design.xml/UseUtilityClass">
    <properties>
        <property name="ignoredAnnotations"
            value="org.springframework.boot.autoconfigure.SpringBootApplication"/>
        </properties>
</rule>

ou ainda propriedades específicas (ex: “allowPrivate”),

<rule ref="category/java/bestpractices.xml/ArrayIsStoredDirectly">
    <properties>
        <property name="allowPrivate" value="true" />
    </properties>
</rule>

Ruleset

Um ruleset é um arquivo xml que é possível definir e configurar as rules desejadas.

<?xml version="1.0"?>

<ruleset name="Regras customizadas"
    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 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">

    <description>
        Minhas regras customizadas
    </description>


    <!-- Suas regras vem aqui -->

</ruleset>

Também é possível excluir ou incluir diretórios e/ou arquivos no arquivo do ruleset, adicionando (normalmente antes das rules) as tags:

<exclude-pattern>.*/some/package/.*</exclude-pattern>
<exclude-pattern>.*/some/other/package/FunkyClassNamePrefix.*</exclude-pattern>
<include-pattern>.*/some/package/ButNotThisClass.*</include-pattern>

Também é possível fazer exclusões de rules nas rules que englobam categorias inteiras:

<rule ref="category/java/codestyle.xml">
    <exclude name="WhileLoopsMustUseBraces"/>
    <exclude name="IfElseStmtsMustUseBraces"/>
</rule>

obs: cuidado ao declarar as rules, o pmd pode passar codigo com erros por uma linha que foi mal escrita, disparando o erro

> Task :pmdMain
Cannot load ruleset C:\Users\rsp\Documents\Codelab\spring-boot-template\config\pmd\ruleset.xml: 3.1
No rules found. Maybe you misspelled a rule name? 

Se desconfiar, faça um teste simples. Adicione a regra <rule ref="category/java/bestpractices.xml/AvoidStringBufferField" /> e instancie a classe StringBuilder em um arquivo e faça ./gradlew check. Ele deve gerar erro no PMD.

obs 2: tem uma seção com exemplos

obs 3: É possível usar rulesets diferentes por contexto (main, teste)

obs 4: É possível fazer arquivo de teste para a nova rule criada

Estratégia de uso do PMD

O PMD será usado primeiro deixando todas as rules habilitadas. No arquivo de ruleset desse projeto, foram suprimidas as rules que deram erro e todas que tinham properties específicas, mas depois elas foram acrescentadas denovo. Isso foi feito para não dar aviso de rules declaradas duas vezes. Conforme for escrevendo os códigos dos projetos, o desenvolvedor usa o comando “./gradlew check” para analisar o código. Se levantar erro, temos que nos perguntar.

  1. A regra faz sentido? É possível a suspender?
  2. Se a regra faz sentido, é possível suspender o arquivo?
  3. Se o arquivo como um todo faz sentido, é possível suspender o método ou classe específicos?
  4. Consertar o código.

Workflow

Exemplos de rulesets

geogebra (Define muitas regras e usa custom rule)

fdroid (Separa em dois rulesets)

wa-task-management (usa com owasp)

allwpilib (exemplo de custom rule)

rulesets de terceiros da documentação

Referências

pmd docs

pmd gradle plugin

pmd github

xpath cheatsheet (resumo de propriedades do XPath)

Using PMD in a Gradle Build - Darren Codes

Baeldung

Code cop