Metodología - Checkstyle - Tengamos el mismo estilo

22/01/2018 6-minute read

Hola Programador@s:

¿Quién sabe qué es el CheckStyle?

Fue por allá en septiembre de 2016 cuando formé parte de un grupo de desarrolladores con mucha mas experiencia que yo en la programación en grupo.

El CTO “impuso” de buen rollo ciertas directrices, entre ellas figuraba el CheckStyle (algo desconocido para mi hasta ese momento).

Se trataba de aplicar en nuestros entornos de desarrollo un sistema que comprobaba que todos las clases que se escribían tenían el mismo estilo para todos los programadores, es una comprobación sintáctica y casi tipográfica.

Además, de esta forma, a cualquier programador del grupo le resultará familiar leer el código de otro compañero.

Ahora en 2018, y con un nuevo proyecto, esta vez en solitario, decido aplicar aquellas buenas prácticas.

Configuración Gradle e IntelliJ

En esta ocasión usando Gradle e IntellliJ estos fueron mis pasos a seguir

Por un lado tuve que añadir a IntelliJ el siguiente plugin que nos permite establecer una configuración mediante un archivo checkstyle.xml que se debe encontrar en:

{raiz_del_proyecto}/config/checkstyle/checkstyle.xml

El plugin que debemos aplicar es http://checkstyle.sourceforge.net/

En este enlace podéis encontrar el manual de instalación para IntelliJ http://checkstyle.sourceforge.net/idea.html

En el momento de activar este plugin, automáticamente, cada vez que abrimos una clase, el IDE nos hace una revisión automática y nos indica cuales son los errores (es decir qué sintaxis no cumplen con nuestra propia configuración)

El tema ahora es que toca hacer que Gradle también nos haga esta comprobación antes de compilar, por si se nos ha escapado algo.

Debemos apligar la siguiente configuración.

apply plugin: 'checkstyle'
checkstyle {
	configFile = file('config/checkstyle/checkstyle.xml')
}

Una vez que empecemos a trabajar con nuestro CheckStyle veremos que en determinados momentos nos puede interesar que algunas de las restricciones/comprobaciones no se apliquen.

Para hecho necesitaremos añadir una notación que puede ser a nivel de clase, método o parámetro.

Por ejemplo, en la clase principal, en un aplicación Spring Boot, es necesario que la comprobación HideUtilityClassConstructor no sea aplicada, es decir, que las clases estáticas que por lo general no deberían tener un constructor accesible, en esta única clase sí lo permita, ya que esto va en contra de lo que necesita Spring.

Tendríamos que aplicar la anotación a la clase o al método.

@SuppressWarning

Pero si queremos ser más concretos, sería conveniente limitarlo sólo a HideUtilityClassConstructor y que el resto de comprobaciones sí sean aplicadas. Como es el caso de que los parámetros de los métodos deben ser todos finales.

Configuración CheckStyle

Para hecho necesitamos añadir unos parámetros concretos a nuestro checkstyle.xml

<module name="Checker">
          <module name="SuppressWarningsFilter"/>

Y esto más abajo

<module name="TreeWalker">
          <module name="SuppressWarningsHolder"/>

por último a las configuraciónes que queramos poder suprimir hay que darles un id:

<module name="HideUtilityClassConstructor"/>

debe añadirse el id en minúsculas

<module name="HideUtilityClassConstructor">
     <property name="id" value="checkstyle:hideutilityclassconstructor"/>
</module>

Esto nos permitirá colocar anotaciones del siguiente estilo:

@SuppressWarnings("checkstyle:hideutilityclassconstructor")
@SpringBootApplication
public class WebappApplication {
        public static void main(final String[] args) {
              SpringApplication.run(WebappApplication.class, args);
        }
}

Si queremos aplicar varias supresiones se debe indicar de la siguiente manera, como array de cadenas con {} y separado por , :

@SuppressWarnings({"checkstyle:hideutilityclassconstructor","checkstyle:magicnumbers"})
Nota importante:

En el caso de Spring Boot, no aplicar esta supresión y modificar el método main a private nos provocaría el siguiente error de compilación:

:bootJar FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ‘:bootJar’.
> The value of a manifest attribute must not be null (Key=Start-Class).

Porque el compilador no encuentra la clase principal de la aplicación

Resultado

A continuación os muestro mi checkstyle actual con algunas de las configuraciones suprimibles. Es un checkstyle que circula por la comunidad de desarrolladores con algunos cambios propios.

<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
        "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
        "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">

<!--

  Checkstyle configuration that checks the sun coding conventions from:

    - the Java Language Specification at
      http://java.sun.com/docs/books/jls/second_edition/html/index.html

    - the Sun Code Conventions at http://java.sun.com/docs/codeconv/

    - the Javadoc guidelines at
      http://java.sun.com/j2se/javadoc/writingdoccomments/index.html

    - the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html

    - some best practices

  Checkstyle is very configurable. Be sure to read the documentation at
  http://checkstyle.sf.net (or in your downloaded distribution).

  Most Checks are configurable, be sure to consult the documentation.

  To completely disable a check, just comment it out or delete it from the file.

  Finally, it is worth reading the documentation.

-->

<module name="Checker">
    <module name="SuppressWarningsFilter"/>
    <!--
        If you set the basedir property below, then all reported file
        names will be relative to the specified directory. See
        http://checkstyle.sourceforge.net/5.x/config.html#Checker

        <property name="basedir" value="${basedir}"/>
    -->

    <property name="fileExtensions" value="java, properties, xml"/>

    <!-- Checks whether files end with a new line.                        -->
    <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
    <module name="NewlineAtEndOfFile"/>

    <!-- Checks that property files contain the same keys.         -->
    <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
    <module name="Translation"/>

    <!-- Checks for Size Violations.                    -->
    <!-- See http://checkstyle.sf.net/config_sizes.html -->
    <module name="FileLength"/>

    <!-- Checks for whitespace                               -->
    <!-- See http://checkstyle.sf.net/config_whitespace.html -->
    <module name="FileTabCharacter"/>

    <!-- Miscellaneous other checks.                   -->
    <!-- See http://checkstyle.sf.net/config_misc.html -->
    <module name="RegexpSingleline">
        <property name="format" value="\s+$"/>
        <property name="minimum" value="0"/>
        <property name="maximum" value="0"/>
        <property name="message" value="Line has trailing spaces."/>
    </module>

    <!-- Checks for Headers                                -->
    <!-- See http://checkstyle.sf.net/config_header.html   -->
    <!-- <module name="Header"> -->
    <!--   <property name="headerFile" value="${checkstyle.header.file}"/> -->
    <!--   <property name="fileExtensions" value="java"/> -->
    <!-- </module> -->

    <module name="TreeWalker">
        <module name="SuppressWarningsHolder"/>
        <!-- Checks for Naming Conventions.                  -->
        <!-- See http://checkstyle.sf.net/config_naming.html -->
        <module name="ConstantName"/>
        <module name="LocalFinalVariableName"/>
        <module name="LocalVariableName"/>
        <module name="MemberName"/>
        <module name="MethodName"/>
        <module name="PackageName"/>
        <module name="ParameterName"/>
        <module name="StaticVariableName"/>
        <module name="TypeName"/>

        <!-- Checks for imports                              -->
        <!-- See http://checkstyle.sf.net/config_import.html -->
        <module name="AvoidStarImport"/>
        <module name="IllegalImport"/> <!-- defaults to sun.* packages -->
        <module name="RedundantImport"/>
        <module name="UnusedImports">
            <property name="processJavadoc" value="false"/>
        </module>

        <!-- Checks for Size Violations.                    -->
        <!-- See http://checkstyle.sf.net/config_sizes.html -->
        <module name="LineLength">
            <property name="max" value="220"/>
        </module>
        <module name="MethodLength"/>
        <module name="ParameterNumber">
            <property name="max" value="25"/>
        </module>

        <!-- Checks for whitespace                               -->
        <!-- See http://checkstyle.sf.net/config_whitespace.html -->
        <module name="EmptyForIteratorPad"/>
        <module name="GenericWhitespace"/>
        <module name="MethodParamPad"/>
        <module name="NoWhitespaceAfter"/>
        <module name="NoWhitespaceBefore"/>
        <module name="OperatorWrap"/>
        <module name="ParenPad"/>
        <module name="TypecastParenPad"/>
        <module name="WhitespaceAfter"/>
        <module name="WhitespaceAround"/>

        <!-- Modifier Checks                                    -->
        <!-- See http://checkstyle.sf.net/config_modifiers.html -->
        <module name="ModifierOrder"/>
        <module name="RedundantModifier"/>

        <!-- Checks for blocks. You know, those {}'s         -->
        <!-- See http://checkstyle.sf.net/config_blocks.html -->
        <module name="AvoidNestedBlocks"/>
        <module name="EmptyBlock"/>
        <module name="LeftCurly"/>
        <module name="NeedBraces"/>
        <module name="RightCurly"/>

        <!-- Checks for common coding problems               -->
        <!-- See http://checkstyle.sf.net/config_coding.html -->
        <module name="AvoidInlineConditionals"/>
        <module name="EmptyStatement"/>
        <module name="EqualsHashCode"/>
        <module name="HiddenField">
            <property name="id" value="checkstyle:hiddenfield"/>
            <property name="ignoreConstructorParameter" value="true"/>
        </module>
        <module name="IllegalInstantiation"/>
        <module name="InnerAssignment"/>
        <module name="MagicNumber">
            <property name="id" value="checkstyle:magicnumber"/>
        </module>
        <module name="MissingSwitchDefault"/>
        <module name="SimplifyBooleanExpression"/>
        <module name="SimplifyBooleanReturn"/>

        <!-- Checks for class design                         -->
        <!-- See http://checkstyle.sf.net/config_design.html -->
        <module name="FinalClass"/>
        <module name="HideUtilityClassConstructor">
            <property name="id" value="checkstyle:hideutilityclassconstructor"/>
        </module>
        <module name="InterfaceIsType"/>
        <module name="VisibilityModifier">
            <property name="protectedAllowed" value="true"/>
        </module>
        <!-- Miscellaneous other checks.                   -->
        <!-- See http://checkstyle.sf.net/config_misc.html -->
        <module name="ArrayTypeStyle"/>
        <module name="FinalParameters"/>
        <module name="UpperEll"/>

        <!--We can't commit System.outs... Use logging instead -->
        <module name="RegexpSinglelineJava">
            <!-- . matches any character, so we need to
                 escape it and use \. to match dots. -->
            <property name="format" value="System\.out\.println"/>
            <property name="ignoreComments" value="true"/>
        </module>

    </module>

</module>

Espero que os sea de utilidad.

Posts in this Series