[Qualidade de código] Como o PHPMD pode ajudar a tornar seu código melhor

[Qualidade de código] Como o PHPMD pode ajudar a tornar seu código melhor

Da série "Qualidade de código", hoje vou apresentar como a análise estática PHPMD pode lhe ajudar

Afinal, o que é a série qualidade de código?

É uma série de artigos que dão dicas de como melhorar a qualidade do seu código, com dicas teóricas, dicas de ferramentas para facilitar nosso dia a dia como desenvolvedor. Mas focado em desenvolvimento em PHP. Essa série está sendo uma continuação do primeiro artigo publicado por mim, pode ser encontrado aqui: "Como mensurar a qualidade de código php"

O que é PHPMD?

PHPMD é uma ferramenta de análise estática, com essa ferramenta é possível validar alguns pontos como:

  • Visualização de métodos não utilizados;
  • Visualização de propriedades não utilizadas;
  • Visualização de parâmetros não utilizados;
  • Visualização de possíveis bugs;
  • Verificação de complexidade do código;

Com essas validações do PHPMD é possível criar um código limpo e de fácil entendimento.

Para a demonstração iniciei um código usando docker com Lumen, para isso tenho dois containers:

  • api.estudo-lumen.dev - Onde roda o nginx;
  • app.estudo-lumen.dev - Onde roda o PHP com composer;

Caso esteja utilizando a estrutura com docker, acesse seu container com o PHP, caso não esteja utilizando docker, apenas entre com o terminal em seu projeto e vamos executar os seguintes comandos:

composer require --dev phpmd/phpmd

Após ser instalado o phpmd e suas dependências, a utilização do phpmd é simples, temos que chamar o phpmd com 3 argumentos:

  • $1 pasta dos códigos -> temos que informar qual arquivo queremos que seja analisado, caso queira que todo o código seja analisado, apenas insira ".", mas caso queira um arquivo específico insira o caminho "app/Services/ServicoQualquer.php".

  • $2 retorno dos erros -> é necessário informar qual o tipo do retorno dos problemas, temos 3 tipos para ser utilizado, xml, text, html, json, ansi, github, sarif, checkstyle.

  • $3 regras para verificar -> por último temos que passar quais regras gostaríamos que fosse verificado, atualmente temos disponíveis:

  • Cleancode -> Aplica regras que impõem um código limpo;
  • Code size -> Aplica regras verificando o tamanho do código;
  • Controversial -> Aplica regras analisando divergências no código;
  • Design -> Aplica regras analisando o design do código;
  • Naming -> Aplica regras analisando o tamanho de variáveis, métodos;
  • Unused code -> Aplica regras analisando códigos que não são utilizados;

Caso queira verificar o que é considerado em cada regra, no site oficial tem a explicação de todas as regras.

Para testarmos criei um método no controller chamado teste e junto uma variável chamada $teste ficando dessa maneira:

class Controller extends BaseController
{
    public function teste()
    {
        $teste = "";
    }
}

Vamos verificar esta classe utilizando esse comando:

./vendor/bin/phpmd app/Http/Controllers/Controller.php text cleancode,codesize,controversial,design,naming,unusedcode

Ou seja, instanciamos o phpmd, o arquivo a ser verificado Controller.php, o tipo de retorno text e as regras que desejamos verificar. Ao executarmos o comando, temos como retorno o seguinte texto:

app/Http/Controllers/Controller.php:11  UnusedLocalVariable  Avoid unused local variables such as '$teste'.

Foi encontrado um "erro" no arquivo app/Http/Controllers/Controller.php, na linha 11, o tipo de erro é UnusedLocalVariable, ou seja, temos uma variável não utilizada na linha 11 do arquivo Controller.php.

Vamos "arrumar" o ocorrido e realizar novamente a validação, ficando dessa maneira:

class Controller extends BaseController
{
    public function teste()
    {
        $teste = "Olá mundo!";

        return $teste;
    }
}

Executando o mesmo comando executado anteriormente, o nosso console fica vazio, ou seja, as validações não encontraram nenhum ponto de atenção.

Mas caso alguma parte do código, seja realmente necessário para o desenvolvimento do código e infere em alguma verificação, como por exemplo, if else:

class Controller extends BaseController
{
    public function teste()
    {
        $teste = "Olá mundo!";

        if ($teste == null) {
            $teste = 1;
        } else {
            $teste = 0;
        }

        return $teste;
    }
}

Ao realizarmos a validação do PHPMD é retornado o seguinte erro:

/app/Http/Controllers/Controller.php:15  ElseExpression  The method teste uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them.

Mas não podemos retirá-lo do código e não temos ideia de como refatorar, então podemos apenas pedir para o PHPMD ignorar algumas regras e temos duas maneiras de fazer, a primeira é uma maneira mais "bruta", pois ignora todos os tipo de regras, para isto temos que adicionar uma annotation no método:

     /**
     * @return int
     * @SuppressWarnings("PHPMD")
     */
    public function teste()
    {
        $teste = "Olá mundo!";

        if ($teste == null) {
            $teste = 1;
        } else {
            $teste = 0;
        }

        return $teste;
    }

Mas desta maneira é possível que tenha alguma outra regra que seja infringida e não foi percebida. Para isto vamos ignorar somente a regra ElseExpression, ficando desta maneira:

     /**
     * @return int
     * @SuppressWarnings("PHPMD.ElseExpression")
     */
    public function teste()
    {
        $teste = "Olá mundo!";

        if ($teste == null) {
            $teste = 1;
        } else {
            $teste = 0;
        }

        return $teste;
    }

Para ignorar qualquer erro de regra do PHPMD, basta verificarmos a regra mostrada no console e colocar após o PHPMD na annotation.

Mas caso não goste das regras pré-estabelecidas pela biblioteca e queira criar suas próprias regras, também é possível, basta criarmos um arquivo na raiz do projeto chamado rulesets.xml com esta estrutura:

<?xml version="1.0"?>
<ruleset name="PHPMD rule set"
         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>
    Seu xml com suas regras definidas.
  </description>

  <rule ref="rulesets/codesize.xml" />
  <rule ref="rulesets/cleancode.xml" />
  <rule ref="rulesets/controversial.xml" />
  <rule ref="rulesets/design.xml" />
  <rule ref="rulesets/naming.xml" />
  <rule ref="rulesets/unusedcode.xml" />
</ruleset>

Na estrutura acima temos todas as regras setadas, mas para o teste que faremos, vamos apenas utilizar uma, ficando desta maneira:

<?xml version="1.0"?>
<ruleset name="PHPMD rule set"
         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>
        Seu xml com suas regras definidas.
    </description>

    <rule ref="codesize.xml" />
</ruleset>

Vamos criar um novo arquivo na raiz denominado codesize.xml, onde será setado as regras, o arquivo padrão está localizado aqui.

Para o teste, criei um código cheio de if, assim a nossa verificação cairá na regra de CyclomaticComplexity, podemos ver abaixo:

public function teste()
    {
        $teste = "Olá mundo!";

        if ($teste == null) {
            if ($teste == null) {
                if ($teste == null) {
                    if ($teste == null) {
                        if ($teste == null) {
                            if ($teste == null) {
                                if ($teste == null) {
                                    if ($teste == null) {
                                        if ($teste == null) {
                                            if ($teste == null) {
                                                if ($teste == null) {
                                                    if ($teste == null) {
                                                        if ($teste == null) {
                                                            if ($teste == null) {
                                                                echo "dd";
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        return $teste;
    }

Quando temos um arquivo personalizado com as regras, ao chamar o teste, ao invés de chamar o conjunto de regras padrões, temos que chamar o arquivo criado, então fica desta maneira:

./vendor/bin/phpmd app/Http/Controllers/Controller.php text rulesets.xml

Temos como retorno da verificação :

/app/Http/Controllers/Controller.php:12  CyclomaticComplexity  The method teste() has a Cyclomatic Complexity of 15. The configured cyclomatic complexity threshold is 10.

Mas como estamos em nosso conjunto de regras, podemos personalizar a regra, podendo assim alterar o complexidade padrão, de 10 para 16 por exemplo, para isto voltamos para o arquivo codesize.xml.

Neste arquivo temos diversas regras, podemos localizar pelo name das regras, em nosso caso estamos procurando a regra denominada CyclomaticComplexity, então podemos procurar por este nome, ao encontrar temos dentro de properties as propriedades de verificação, no caso particular temos o reportLevel que está com valor de 10, mas estamos com a complexidade no nível 15, então ao alterar o valor de 10 para 16 e executarmos novamente, temos como resultado a não visualização do aviso.

<property name="reportLevel" description="The Cyclomatic Complexity reporting threshold"  value="16"/>

Chegamos ao fim deste artigo, passamos por todos os pontos básicos de utilização do PHPMD, bora colocar essa etapa na pipeline dos devs?