Análisis de código Estático con PMD

(Actualizado Mayo 2020)
Desde hace ya mucho, para los lenguajes de programación más comunes, disponemos de analizadores de código estático. Son herramientas, que aplican reglas de validación al código que escribimos.

Salesforce no proporciona una herramienta nativa dedicada al análisis de código estático APEX. Afortunadamente existen diferentes opciones en el mercado: CodeScan, Checkmarx, PMD, etc. Salesforce ofrece scans ad-hoc mediante Force.com Code Scanner Portal en asociación con Checkmarx, que es la herramienta utilizada internamente en Salesforce.

Una de estas herramientas muy empleada en otro lenguajes de programación es PMD. PMD es una librería Open Source para la ejecución de reglas de validación sobre un lenguaje de programación.

En Abril de 2016, Robert Sösemann anunció que había portado muchas reglas de validadción sobre el lenguaje Apex a PMD. Desde entonces PMD se ha convertido en una herramienta crucial en muchos proyectos de Salesforce, dada su facilidad de uso y la calidad de las reglas utilizadas.

PMD no requiere de ninguna infraestructura adicional, solo descargar los binarios y ejecutarlos, en nuestro ordenador, o en un servidor, con unda JDK instalada etc.

Las capacidades de PMD, su licenciamiento, versatilidad, extensibilidad, integración con los IDEs y con  herramientas de CI habituales (Maven, Ant, Gradle, etc.), y comunidad facilita enormemente su adopción.

Por ello, si no lo conoces, creo que puede ser muy útil, y es una herramienta muy a tener en cuenta para utilizar en nuestros proyectos. Te animo a leer el artículo por si te puede ayudar.

Qué es PMD

PMD se define como An extensible cross-language static code analyzer, y en pocas palabras, es una herramienta Open Source que permite ejecutar reglas de análisis de código estático en varios lenguajes, entre ellos Apex.

Desde entonces, varias empresas, como la propia Salesforce,  aportan código, adaptaciones del lenguaje, nuevas reglas, etc.

Anuncio de la disponibilidad de PMD con Apex:

Definición del propio autor del port sobre PMD, que creo es bastante auto-explicativo:

En el momento de escritura de este artículo (Julio 2018 – revisado Mayo 2020) la versión actual de PMD es la 6.24 (aunque ya se está desarrollanado activamente la versión 7.

Funcionamiento básico de PMD

Desde el punto de vista del usuario, usamos PMD de 4 formas distintas:

  1. Mediante linea de comandos (CLI)
  2. Integrado en un entorno de desarrollo como Visual Studio Code, Welkin Suite, Intellij, Eclipse, etc.
  3. Formando parte de un sistema de Integración Continua que posean un orquestador como Maven, Ant, Gradle, etc.
  4. Formando parte de un producto comercial en Cloud u OnPremise como Codacy, Code-Scan, etc.

Las características principales de PMD son :

  • Requiere de una lista de reglas, que alguien ha definido y programado previamente para detectar incumplimientos.
  • Estas reglas detectan situaciones que queremos anunciar, asignando un mensaje de error/advertencia/información, y un valor de prioridad/severidad.
  • Al invocar a PMD sobre una estructura de ficheros, la herramienta examina fichero a fichero, y va aplicando las reglas y produciendo los resultados de los incumplimientos encontrados.
  • El conjunto de reglas definido se denomina Ruleset. De la calidad de las rules, de la selección que hayamos realizado para nuestro proyecto, de como las entendamos, y del proceso de mejora continua que hayamos pensado, dependerá el éxito que tendremos en el uso de PMD.
  • El informe resultado, puede ser obtenido en varios formatos: fichero plano, XML, HTML, dentro de una herramienta, etc.

Ruleset definido por la comunidad y como está organizados

Existe un ruleset ofrecido por la comunidad para PMD-Apex.
Está dividido en las siguientes áreas de actuación:

  1. Best Practices: engloba las reglas que examinan el uso de buenas prácticas aceptadas mayoritariamente.
  2. Code Style: engloba las reglas que examinan el código para que comprueban el cumplimiento de cierto estilo de código.
  3. Documentation: reglas que validan que exista documentación en formato ApexDoc, en clases, ficheros, etc..
  4. Design: reglas que nos permiten descubrir problemáticas de diseño (cada uno entiende diseño de manera distinta).
  5. Error Prone: reglas que permiten detectar situaciones propicias a errores en tiempo de ejecución o confusas.
  6. Performance: reglas que permiten detectar código que no permite la ejecución bajo condiciones de rendimiento aceptables.
  7. Security: reglas que detectan potenciales problemas de seguridad.
  8. Additional rulesets: lo que viene a ser un cajón de sastre para compatibilidad con versiones anteriores.

A continuación, con el siguiente gráfico, podemos observar el peso de cada área, en cuanto a la tipología de las reglas:

Distribución de rules por Área de análisis

Hagamos un repaso rápido de cada una de las reglas, y así obtener una idea de lo que pretenden cubrir en cada caso.

Reglas asociadas a Best Practices

  1. ApexAssertionsShouldIncludeMessage:
  2. ApexUnitTestClassShouldHaveAsserts
  3. ApexAssertionsShouldIncludeMessage
  4. ApexUnitTestMethodShouldHaveIsTestAnnotation
  5. ApexUnitTestShouldNotUseSeeAllDataTrue
  6. AvoidGlobalModifier
  7. AvoidLogicInTrigger
  8. DebugsShouldUseLoggingLevel
  9. UnusedLocalVariable

Reglas asociadas al área de Code Style

  1. ClassNamingConventions
  2. FieldDeclarationsShouldBeAtStart
  3. FieldNamingConventions
  4. ForLoopsMustUseBraces
  5. FormalParameterNamingConventions
  6. IfElseStmtsMustUseBraces
  7. IfStmtsMustUseBraces
  8. LocalVariableNamingConventions
  9. MethodNamingConventions
  10. PropertyNamingConventions
  11. VariableNamingConventions
  12. WhileLoopsMustUseBraces

Reglas asociadas al área de Design

  1. AvoidDeeplyNestedIfStmts
  2. CognitiveComplexity: esta regla intenta medir la dificultad de lectura del código escrito. Esta regla fue aportada por Sonar, disponible más información en este enlace.
  3. CyclomaticComplexity: examina la complejidad de un método, enumerando sus  puntos de decisión, estos son, los puntos donde el código puede saltar a otro sitio, típicamente las operaciones que realizan saltos son: if, while, for, case, etc. Para ello se asignan los siguientes rangos y se produce un aviso para los métodos cuya complejidad sea alta (que deberían ser seccionados en métodos auxiliares y disminuir así la complejidad). Los rangos establecidos son:
    1. Puntuación  1-4: complejidad baja
    2. Puntuación 5-7: complejidad moderada
    3. Puntuación 8-10: complejidad alta
    4. Puntuación 11+: complejidad muy alta
  4. ExcessiveClassLength
  5. ExcessiveParameterList
  6. ExcessivePublicCount
  7. Las siguientes hacen referencia a NCSS (Non-Commenting Source Statements):

  8.  NcssConstructorCount
  9. NcssMethodCount
  10. NcssTypeCount
  11. StdCyclomaticComplexity
  12. TooManyFields

Regla asociada al área de Documentación

  1. ApexDoc

Reglas asociadas al área de Error Prone

  1. ApexCSRF
  2. AvoidDirectAccessTriggerMap
  3. AvoidHardcodingId
  4. AvoidNonExistentAnnotations
  5. EmptyCatchBlock
  6. EmptyIfStmt
  7. EmptyStatementBlock
  8. EmptyTryOrFinallyBlock
  9. EmptyWhileStmt
  10. MethodWithSameNameAsEnclosingClass
  11. TestMethodsMustBeInTestClasses

Reglas asociadas al área de Performance

  1. AvoidDmlStatementsInLoops
  2. AvoidSoqlInLoops
  3. AvoidSoslInLoops

Reglas asociadas al área de Security

  1. ApexBadCrypto
  2. ApexCRUDViolation
  3. ApexCSRF
  4. ApexDangerousMethods
  5. ApexInsecureEndpoint
  6. ApexOpenRedirect
  7. ApexSharingViolations
  8. ApexSOQLInjection
  9. ApexSuggestUsingNamedCred
  10. ApexXSSFromEscapeFalse
  11. ApexXSSFromURLParam
RulesLimitsSalesforce
Dado el carácter Multi-tenant, Salesforce requiere del establecimiento de límites para su correcto funcionamiento. El uso de reglas que validen las buenas prácticas establecidas, nos ahorrará tiempo y malas ratos.

El Ruleset: listado de las reglas a aplicar

El ruleset, es un fichero XML, que define el conjunto de reglas que aplicaremos al ejecutar un análisis de código.
En la siguiente porción de código vemos la definición de un ruleset:

  • Un ruleset tiene atributos (description) y  la secuencia de las rules que lo componen.
  • Cada regla contiene su referencia, atributos (como por ejemplo el name y la priority) y propiedades (formadas por claves-valor que determinan cierto comportamiento de la regla).
<?xml version="1.0" encoding="UTF-8"?>
<ruleset xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Default ruleset para desarrolladores del proyecto X" xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">

<description>Ruleset durante etapa de desarrollo</description>

   <rule ref="category/apex/design.xml/ExcessiveClassLength" message="Evitar clases super largas de código (líneas de código)">
      <priority>3</priority>
      <properties>
         <property name="minimum" value="1000" />
      </properties>
   </rule>
   <rule ref="category/apex/design.xml/ExcessiveParameterList" message="Evitar métodos con una lista numerosa de parámetros">
      <priority>3</priority>
      <properties>
         <property name="minimum" value="4" />
      </properties>
   </rule>
...
</ruleset>

Como he comentado justo al inicio del artículo, la comunidad ofrece un ruleset definido para Apex PMD,  pero es fundamental, que en nuestro proyecto acordemos nuestro ruleset, y si tenemos suficiente expertise, adaptar y/o construir nuestras propias reglas.

Cómo configurar nuestro propio Ruleset

Veamos ahora como configurar nuestro propio ruleset. Para ello, utilizaremos como base las reglas definidas por la comunidad en el ruleset por defecto. El esqueleto básico del ruleset es:

<?xml version="1.0"?>

<ruleset name="Custom Rules" 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>
        Nuestro propio ruleset
    </description>
    <!-- Definición de reglas y parámetros -->
</ruleset>

Añadir una regla o un conjunto de reglas

Podemos añadir reglas predefinidas o un conjunto completo de reglas directamente (de las que hemos visto antes, dado que estamos trabajando con las pre-definidas por PMD):

<!-- Así añadimos una única regla: En este caso la ExcessiveParameterList que pertenece al bloque de Design -->
<rule ref="category/apex/design.xml/ExcessiveParameterList" />
<!-- Y así añadimos una conjunto completo de reglas: En este caso, para seguir con el ejemplo, añado todas las del bloque de Design -->
<rule ref="category/apex/design.xml" />
<!-- Podemos tunear mediante el uso de exclude, de manera que ahora las añadíriamos todas menos ExcessiveParameterList -->
<rule ref="category/apex/design.xml/ExcessiveParameterList" >
  <exclude name="ExcessiveParameterList"/>
</rule>

Usando lo que se denominaPatterns, podemos simplificar de manera radical el ruleset, veamos ejemplos básicos, al utilizar comodines:

<?xml version="1.0"?>
<ruleset name="myruleset" 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>Incluimos 2</description>

   <!-- Mediante exclude-pattern e include-pattern referencimos patrones basándonos en los nombres de las referencias reglas-->

   <exclude-pattern>category/apex/design.xml/Excessive*</exclude-pattern>
   <include-pattern>category/apex/design.xml/Ncss*</include-pattern>   
   <include-pattern>category/apex/security.xml/Apex*</include-pattern>

   <!-- A partir de aquí definimos la reglas individuales -->

</ruleset>

Nota: el esquema es exactamente igual para el resto de lenguajes soportados por PMD!!

Cómo adecuar un regla para adecuarla a nuestros criterios de criticidad

Podemos modificar los valores de los atributos de cada regla para que se adecue a nuestras preferencias, como:

  • El Mensaje que aparezca al incumplir una regla
  • El valor sobre la Prioridad que deseamos asignar a ese positivo

Aquí va un ejemplo muy sencillo de cómo hacerlo:

  • Modificamos la regla EmptyIfStmt para que refleje un texto distinto, y además para que en Visual Studio Code se muestre, como Error en lugar de solo Warning.
  • Para ello modifico la Prioridad a 1.

En el ruleset dejo la regla así:

<rule ref="category/apex/errorprone.xml/EmptyIfStmt" message="Esteve por dios, implementa los IFs">
      <priority>1</priority>
</rule>

El resultado es el esperado, por ejemplo en VSCode, vemos, como se muestra el mensaje que hemos tuneado y el incumplimiento se muestra como grave:

Para Intellij aparece de forma parecida:

Como evitar que ciertas partes del código se examinen

Por los motivos que sean, podemos requerir que ciertas partes del código sean ignoradas por PMD. Mediante la anotación @SuppressWarnings podemos ignorar todas o algunas reglas en una clase.

En este primer ejemplo, voy a suprimir la comprobación de todas las reglas en la clase:


// Ignorar cualquier validación para PMD
@SuppressWarnings('PMD')
public class ForceGraells {
    void leer() {}
}

Alternativamente se puede utilizar el comentario //NOPMD (disponible desde la versión 6.22)

En este otro ejemplo, voy a suprimir solo la comprobación de 2 reglas concretas:


// Ignorar 2 reglas concretas
@SuppressWarnings('PMD.ExcessiveParameterList,  
                   PMD.EmptyStatementBlock')
public class ForceGraells {
    void leer(String a, String b, String c, String d, String e) {
        //sin cuerpo
    }
}

Veamos un ejemplo de como usar la anotación @SuppressWarnings y su resultado en Intellij:

Detector de código copiado

Finalmente, dentro del mismo bundle de herramientas, el proyecto PMD ofrece una herramienta para detección de código duplicado, que puede ser muy útil para detectar esas situaciones que nos pueden haber pasado por alto.

Mediante varios parámetros de configuración podemos afinar la búsqueda de segmentos de código duplicado. A continuación adjunto un ejemplo del resultado ofrecido por cpd:

Found a 8 line (16 tokens) duplication in the following files: 
Starting at line 3 of /home/egraells/Dropbox/workspace/triggerClaseDinamica/triggerClaseDinamica/force-app/main/default/classes/ForceGraells.cls
Starting at line 12 of /home/egraells/Dropbox/workspace/triggerClaseDinamica/triggerClaseDinamica/force-app/main/default/classes/ForceGraells.cls

    public void leer(){

        boolean durmiendo = false;
        if (!durmiendo){
            //System.debug('Este codigo nunca se ejecuta');
        }   
        //Petete: una sólida cultura es la herencia más segura
    }
=====================================================================

Mostrar los resultados en el formato deseado

PMD permite obtener los resultados de los análisis en diferentes formatos o incluso, que podamos construir nuestro propio formateador.
Actualmente, existen disponibles los siguientes formateadores (disponibles en esta página:

  1. codeclimate
  2. csv
  3. emacs
  4. html
  5. ideaj
  6. json
  7. summaryhtml
  8. text (default)
  9. textcolor
  10. textpad
  11. vbhtml
  12. xml
  13. xslt
  14. yahtml

Recomendaciones personales

  • Para evaluar fast&furious PMD para Apex utilízalo dentro de tu IDE, hay plugins para casi todos ellos y así simplificarás el proceso.
  • Utiliza la  opción -cache para acelerar el análisis.
  • Utiliza con exquisita precaución la opción @SuppressWarnings. Debe haber una buena razón acordada.
  • Puedes empezar modificando el  ruleset comunitario, para cambiar textos y prioridades, y partir de ahí con confianza, tunearlo.

Conclusiones

El análisis de código estático es imprescindible en cualquier proyecto (***).

Existen otras herramientas en el mercado muy conocidas, como SonarQube, Code-Scan, Clayton, Codacy, Checkmarx, etc., que proporcionan funcionalidades adicionales, pero que requieren de más infraestructura  y/o costes adicionales (aunque te animo por supuesto a evaluarlas igualmente).

Por ello,  si áun no estás usando ninguna, creo que PMD es la herramienta ideal para iniciarte, dado que con el respaldo de la comunidad y de la propia Salesforce, permiten a cualquier desarollador usarlo sin coste y en muy breve tiempo.

Espero que te sirva y si te encuentras a Robert por la calle, no olvides agradecérselo!