Análisis estático de código Java con Spotless y Error Prone

Escrito por picodotdev el .
java
Enlace permanente Comentarios

Mantener la consistencia en el código de un equipo no debería depender de la disciplina individual de cada desarrollador. El análisis estático de código automatiza esta tarea, desde verificar que se siguen las convenciones de formateo hasta detectar patrones que pueden convertirse en errores en producción. En este artículo repaso las herramientas disponibles para Java, comparo las opciones más habituales, Checkstyle, PMD y Spotbugs con alternativas como Spotless y Error Prone, y explico cómo integrarlas en el flujo de trabajo con Gradle.

Java

Cada lenguaje de programación define unas convenciones para formatear el código, reglas de espaciado, orden de elementos, longitud de línea, entre otras. Seguirlas garantiza que cualquier desarrollador familiarizado con el lenguaje pueda leer el código sin fricción. A partir de esas convenciones estándar, los equipos pueden acordar ajustes propios, como aumentar la longitud máxima de línea si trabajan con monitores de alta resolución o adoptar alguna convención adicional que mejore la legibilidad en su contexto.

Aunque el formateo pueda parecer un detalle menor, no lo es. Todo editor moderno lo aplica de forma automática con muy poco esfuerzo, por lo que no tenerlo en orden es una señal de que el código no se está manteniendo con el cuidado necesario. Su importancia radica en la legibilidad, dado que los desarrolladores pasamos la mayor parte del tiempo leyendo código, ya sea propio o ajeno, un código bien formateado reduce la carga cognitiva y facilita la comprensión.

Más allá del formateo, existe otro tipo de herramientas orientadas a detectar errores simples pero frecuentes que pueden manifestarse en tiempo de ejecución o que introducen ruido innecesario en el código, imports no utilizados, variables declaradas pero nunca referenciadas, bloques catch vacíos que silencian excepciones, entre otros.

El análisis estático de código consiste en examinar el código fuente tal y como está escrito, sin necesidad de ejecutarlo. Dependiendo de la herramienta, este análisis puede cubrir distintos objetivos, verificar que se siguen las convenciones de estilo, detectar patrones de código propensos a errores, o identificar el uso de librerías con vulnerabilidades de seguridad conocidas. Para Java existen varias opciones que cubren uno o varios de estos ámbitos, y que se pueden combinar para obtener una cobertura más completa.

Formateo de código

Las convenciones de formateo más utilizadas en Java son las propias del lenguaje, definidas originalmente por Sun Microsystems, y las de Google, que introduce algunos ajustes sobre las anteriores. Como desarrolladores podemos usarlas como base para crear nuestra propia guía de estilo. Las dos primeras establecen una longitud máxima de línea de entre 80 y 100 caracteres, un límite que tiene su origen en las pantallas de terminal de los años 80 y que hoy resulta restrictivo.

Con la verbosidad característica de Java, nombres de clases largos, genéricas, llamadas encadenadas, y los monitores actuales de alta resolución, una línea de 120 caracteres se queda corta con frecuencia. Por preferencia personal trabajo con 180 caracteres, que permite escribir código más expresivo sin necesidad de romper artificialmente las líneas.

Los formateadores automáticos hacen un buen trabajo en la mayoría de situaciones, pero no siempre producen el resultado más legible cuando el código es complejo o tiene estructuras poco habituales. En esos casos es razonable aplicar un criterio manual. El valor real de estas herramientas no está en la perfección del resultado sino en la consistencia, cuando el equipo comparte las mismas reglas, los comentarios en una code review dejan de centrarse en el formateo y pueden dedicarse íntegramente a la lógica del programa.

Herramientas de análisis estático de código

Esta es la combinación de herramientas de análisis estático de código que he estado usando hasta ahora para mantener la consistencia en el formateo del código y detectar errores básicos en el código. Estas herramientas se solapan un poco entre ellas, PMD al igual que Checkstyle hace algunas comprobaciones de formateo de código.

  • PMD: análisis estático de código con más de 400 reglas.
  • Checkstyle: comprobación de reglas de formateo.
  • Spotbugs: para detectar errores de programación que pueden generar errores en tiempo de ejecución.
  • IntelliJ: para aplicar las reglas de formateo.

No son las únicas opciones disponibles. Preguntando a Claude otras opciones son Spotless y Error Prone en sustitución o combinación de las anteriores que son las que destacaré en este artículo.

Esta es una tabla comparativa de las herramientas.

Herramienta Categoría Formatea Detecta bugs Plugin Gradle
Checkstyle Estilo No No
Spotless Estilo No
PMD Análisis No Parcial
Spotbugs Bugs No
Error Prone Compilador No

Spotless

Checkstyle solo detecta errores de formateo pero no formatea el código, no es un gran problema ya que IntelliJ es capaz de formatear todo el código con muy poco esfuerzo, Spotless realiza ambas funciones, detección de errores y formateo de código. El formateo usando la linea de comandos sin necesidad de utilizar el editor.

Un problema de usar Checkstyle es que la configuración de reglas de formateo están duplicadas, hay que tener unas reglas para IntelliJ y otras reglas equivalentes para Checkstyle de modo que aplicando las reglas desde IntelliJ pasen las validaciones de Checkstyle. Spotless tiene la ventaja de poder usar las mismas reglas y formateador para ambos.

En caso de que Spotless no formatee algún bloque de código como deseamos es posible desactivar el formateador en ciertas regiones, basta con encapsular el bloque de código que no se desea formatear ente dos comentarios spotless:off y spotless:on.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package io.github.picodotdev.blogbitix.holamundospotless;

public class Main {

// spotless:off 
    public static void main(final String[] args) {
        final Greeter greeter = new Greeter();
        System.out.println(greeter.greet("Mundo"));
    }
// spotless:on

}
Main-spotless.java

Error Prone

Error Prone es una analizador estático del código para prevenir errores en tiempo de ejecución. Equivalente a PMD y Spotbugs. Error Prone se define a sí mismo de la siguiente manera.

  • Es común que incluso los mejores programadores cometan errores simples. Y a veces, una refactorización que parece segura puede dejar código que nunca hará lo que se espera.
  • Estamos acostumbrados a recibir ayuda del compilador, pero este no hace mucho más allá de la comprobación estática de tipos. Al usar Error Prone para complementar el análisis de tipos del compilador, puedes detectar más errores antes de que te hagan perder tiempo o se conviertan en errores en producción.

En la siguiente página están listados los errores que detecta Error Prone.

Error Prone es ejecutado en la compilación del código fuente, los errores detectados se reportan como error o warning como si fuera un mensaje de compilador. La anotación @SuppressWarnings permite suprimir un mensaje error que tenga el código, en este caso el de SelfAssignment.

Por ejemplo, hacer una asignación a la misma variable no tiene efecto con lo que posiblemente sea un descuido del programador, Error Prone lo indica como un error a no ser que se suprima la comprobación.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package io.github.picodotdev.blogbitix.holamundospotless;

public class Main {

    @SuppressWarnings("SelfAssignment")
    public static void main(final String[] args) {
        Greeter greeter = new Greeter();
        greeter = greeter;
        System.out.println(greeter.greet("Mundo"));
    }
}
Main.java

Editorconfig

Finalmente, Editorconfig permite definir las reglas de formateo de forma independiente del editor como longitud de líneas, usar espacios o tabuladores y tamaño de la indentación para cada tipo de archivo. IntelliJ tiene la opción de exportar las reglas en este formato aunque algunas opciones no sean interpretables por otros editores.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
max_line_length = 170

[*.{java,kt,groovy,gradle.kts}]
indent_size = 4

[*.{js,ts,jsx,tsx,css,scss,less}]
indent_size = 4

[*.{yml,yaml,json}]
indent_size = 4

[*.{xml,html}]
indent_size = 4

[*.{md}]
indent_size = 4
.editorconfig

Integración en el flujo de trabajo

Spotless y Error Prone tienen un plugin para Gradle lo que permite ejecutar sus reglas al desarrollar y en el entorno de integración continua.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import net.ltgt.gradle.errorprone.errorprone

plugins {
    application
    id("com.diffplug.spotless") version "7.0.2"
    id("net.ltgt.errorprone") version "5.1.0"
}

repositories {
    mavenCentral()
}

dependencies {
    errorprone("com.google.errorprone:error_prone_core:2.28.0")
}

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(21))
    }
}

application {
    mainClass.set("io.github.picodotdev.blogbitix.holamundospotless.Main")
}

spotless {
    java {
        target("src/**/*.java")
        targetExclude("**/build/**", "**/generated/**")

        eclipse().configFile("config/picodotdev.xml")
    }

    kotlinGradle {
        target("*.gradle.kts")
        ktlint("1.3.0").setEditorConfigPath(".editorconfig")
    }

    format("misc") {
        target("*.md", "*.yml", ".gitignore", ".editorconfig")
        trimTrailingWhitespace()
        endWithNewline()
    }
}

tasks.withType<JavaCompile>().configureEach {
    options.encoding = "UTF-8"
    options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror"))

    options.errorprone {
        disableWarningsInGeneratedCode.set(true)
        excludedPaths.set(".*/build/generated/.*")
        disable("StringCaseLocaleUsage")
        error("DefaultCharset", "MissingOverride", "UnusedVariable")
    }
}

tasks.named("check") {
    dependsOn("spotlessCheck")
}
build.gradle.kts

Los comandos para ejecutar Spotless y Error Prone son los siguientes.

1
2
./gradlew spotlessCheck

gradlew-spotlessCheck.sh
1
2
./gradlew compileJava

gradlew-compileJava.sh

Recomendación final

Mi recomendación es usar al menos una para analizar el estilo del código y mantener la consistencia y otra herramienta para detectar código que puede producir errores en  tiempo de ejecución. Con esta recomendación es posible usar las siguientes combinaciones:

  • PMD, Checkstyle y Spotbugs
  • Spotless y Error Prone

Tras mucho tiempo usando la primera opción estoy evaluando la segunda con dos herramientas en vez de tres por el motivo de que cada una se centra en un área específica. Mi preferencia es no incluir las reglas de formateo de código como un hook al realizar un commit ya que añade tiempo para realizar el commit y realiza cambios sin supervisión, aplicar el formateo es simple y rápido desde el editor de código como IntelliJ.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run


Comparte el artículo: