Garantiza un código Android de alta calidad con herramientas de análisis estático
Spanish (Español) translation by Esther (you can also view the original English article)
En el tutorial de hoy, aprenderemos cómo asegurar un código Android de alta calidad en nuestros proyectos utilizando algunas herramientas de análisis de código estático para Java. ¡Veremos Checkstyle, FindBugs, PMD y Android Studio Lint, todas ellas gratuitas y de código abierto!
¿Qué son las herramientas de análisis de código estático?
Se trata de herramientas que analizan el código fuente sin ejecutarlo. El objetivo es encontrar posibles vulnerabilidades, como errores y fallos de seguridad. Un popular analizador de código estático gratuito, como FindBugs, comprueba tu código en función de un conjunto de reglas a las que debería adherirse tu código: si el código no sigue estas reglas, es una señal de que algo puede estar mal. Piensa en las herramientas de análisis de código estático como un compilador adicional que se ejecuta antes de la compilación final en el lenguaje del sistema.
Muchas empresas de software exigen que los proyectos pasen pruebas de análisis de código estático, además de realizar revisiones de código y pruebas unitarias en el proceso de construcción. Incluso los mantenedores de proyectos de código abierto suelen incluir uno o más pasos de análisis de código estático en el proceso de construcción. Así que aprender sobre el análisis estático es un paso importante para escribir código de calidad. No olvides que el análisis de código estático, también conocido como prueba de "caja blanca", no debe considerarse como un sustituto de las pruebas unitarias del código fuente.
En este tutorial, vamos a aprender sobre algunas herramientas populares de análisis estático que están disponibles para Android y Java. Pero primero, vamos a ver algunos de los beneficios de usar el análisis estático.
Beneficios
- Ayuda a detectar posibles errores que incluso las pruebas unitarias o manuales podrían haber pasado por alto.
- Define las normas específicas del proyecto. Por ejemplo, el análisis estático como parte de la cadena de construcción ayuda a los recién llegados a ponerse al día con las normas de código de su nuevo equipo.
- Te ayuda a mejorar tu conocimiento de un nuevo idioma.
- Analiza todo el proyecto, incluidos los archivos que quizá no hayas leído nunca.
Configurar
Todas las herramientas de análisis de código que conoceremos en este tutorial están disponibles como plugins de Gradle, por lo que podemos crear tareas Gradle individuales para cada una de ellas. Usemos un único archivo Gradle que los incluya a todos. Pero antes de eso, vamos a crear una carpeta que contendrá todos nuestros archivos para el análisis de código estático.
Abre Android Studio y dentro del módulo de la aplicación (en la vista de proyecto), crea una nueva carpeta y llámala code_quality_tools. Esta carpeta contendrá los archivos XML para las herramientas de análisis de código, y también tendrá un archivo Gradle, quality.gradle, que ejecutará nuestras tareas de análisis estático.



Finalmente, visita tu build.gradle en la carpeta del módulo de la aplicación e incluye esta línea al final del archivo:
1 |
apply from: '/code_quality_tools/quality.gradle' |
Aquí, nuestro script quality.gradle Gradle se está aplicando con una referencia a su ubicación de archivo local.
Checkstyle
Dadas las reglas que especifiques en un archivo XML para hacer cumplir un estándar de codificación para tu proyecto, Checkstyle hace cumplir esas reglas analizando tu código fuente y las compara con estándares o convenciones de codificación conocidos.
Checkstyle es una herramienta de código abierto que es mantenida activamente por la comunidad. Esto significa que puedes crear tus propios controles personalizados o modificar los existentes para adaptarlos a tus necesidades. Por ejemplo, Checkstyle puede ejecutar una comprobación de los nombres de las constantes (finales, estáticas o ambas) en tus clases. Si los nombres de las constantes no se ajustan a la regla de estar en mayúsculas con las palabras separadas por un guión bajo, el problema se marcará en el informe final.
1 |
// incorrect
|
2 |
private final static String myConstant = "myConstant"; |
3 |
|
4 |
// correct
|
5 |
private final static String MY_CONSTANT = "myConstant"; |
Integración de Checkstyle
Te mostraré cómo integrar Checkstyle en nuestro proyecto de Android Studio y te mostraré un ejemplo práctico.
Primero, necesitamos crear nuestras reglas de codificación. Dentro de checkstyle.xml, creamos algunas reglas de configuración de Checkstyle que se ejecutarán contra nuestro código.
1 |
<?xml version="1.0"?><!DOCTYPE module PUBLIC
|
2 |
"-//Puppy Crawl//DTD Check Configuration 1.2//EN"
|
3 |
"http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
|
4 |
<module name="Checker"> |
5 |
<module name="FileTabCharacter"/> |
6 |
<module name="TreeWalker"> |
7 |
|
8 |
<!-- Checks for Naming Conventions -->
|
9 |
<!-- See http://checkstyle.sourceforge.net/config_naming.html -->
|
10 |
<module name="MethodName"/> |
11 |
<module name="ConstantName"/> |
12 |
|
13 |
<!-- Checks for Imports -->
|
14 |
<!-- See http://checkstyle.sourceforge.net/config_imports.html-->
|
15 |
<module name="AvoidStarImport"/> |
16 |
<module name="UnusedImports"/> |
17 |
|
18 |
<!-- Checks for Size -->
|
19 |
<!-- See http://checkstyle.sourceforge.net/config_sizes -->
|
20 |
<module name="ParameterNumber"> |
21 |
<property name="max" value="6"/> |
22 |
</module>
|
23 |
|
24 |
<!-- other rules ignored for brevity -->
|
25 |
</module>
|
En el código anterior, incluimos las reglas o comprobaciones que queremos que Checkstyle valide en nuestro código fuente. Una de las reglas es AvoidStarImport que, como su nombre indica, comprueba si tu código fuente incluye una sentencia import como java.util.*
. (En su lugar, debes especificar explícitamente el paquete a importar, por ejemplo, java.util.Observable
).
Algunas reglas tienen propiedades, que podemos establecer al igual que hicimos con ParameterNumber, que limita el número de parámetros de un método o constructor. Por defecto, la propiedad max
es 7, pero la hemos cambiado a 6. Echa un vistazo a algunas de las otras comprobaciones en el sitio web de Checkstyle.
Para ejecutar esta comprobación, necesitamos crear una tarea Gradle. Así que visita el archivo quality.gradle y crea una tarea llamada checkstyle:
1 |
apply plugin: 'checkstyle' |
2 |
|
3 |
task checkstyle(type: Checkstyle) { |
4 |
description 'Check code standard' |
5 |
group 'verification' |
6 |
|
7 |
configFile file('./code_quality_tools/checkstyle.xml') |
8 |
source 'src' |
9 |
include '**/*.java' |
10 |
exclude '**/gen/**' |
11 |
|
12 |
classpath = files() |
13 |
ignoreFailures = false |
14 |
}
|
Observa que en el código anterior, primero aplicamos el plugin de Gradle Checkstyle. Le hemos dado una descripción y lo hemos añadido a un grupo de Gradle ya predefinido llamado verificación.
La clave de las propiedades de la tarea Checkstyle Gradle que nos ocupa son:
- configFile: el archivo de configuración de Checkstyle a utilizar.
- IgnoreFailures: permitir o no que la construcción continúe si hay advertencias.
- include: el conjunto de patrones de inclusión.
- exclude: el conjunto de patrones de exclusión. En este caso, no analizamos las clases generadas.
Finalmente, puedes ejecutar el script de Gradle visitando la ventana de la herramienta Gradle en Android Studio, abriendo el grupo de verificación, y luego haciendo clic en checkstyle para ejecutar la tarea.



Otra forma es utilizar la línea de comandos:
1 |
gradle checkstyle |
Una vez que la tarea haya terminado de ejecutarse, se generará un informe, que está disponible en el módulo de aplicación > build > reports > checkstyle. Puedes abrir checkstyle.html para ver el informe.



Hay un plugin de Checkstyle disponible gratuitamente para Android Studio o IntelliJ IDEA. Ofrece un escaneo en tiempo real de sus archivos Java.
PMD
PMD es otra herramienta de análisis de código abierto que analiza tu código fuente. Encuentra fallos comunes como variables no utilizadas, bloques de captura vacíos, creación de objetos innecesarios, etc. PMD tiene muchos conjuntos de reglas que puedes elegir. Un ejemplo de una regla que forma parte del conjunto de reglas de diseño es:
-
SimplifyBooleanExpressions
: evita las comparaciones innecesarias en las expresiones booleanas que complican el código simple. Un ejemplo:
1 |
public class Bar { |
2 |
// can be simplified to
|
3 |
// bar = isFoo();
|
4 |
private boolean bar = (isFoo() == true); |
5 |
|
6 |
public isFoo() { return false;} |
7 |
}
|
PMD se configura con el archivo pmd.xml. Dentro de él, incluiremos algunas reglas de configuración como las de Android, Naming y Design.
1 |
<?xml version="1.0"?>
|
2 |
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Android Application Rules" |
3 |
xmlns="http://pmd.sf.net/ruleset/1.0.0" |
4 |
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd" |
5 |
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"> |
6 |
|
7 |
<description>Custom ruleset for Android application</description> |
8 |
|
9 |
<exclude-pattern>.*/R.java</exclude-pattern> |
10 |
<exclude-pattern>.*/gen/.*</exclude-pattern> |
11 |
|
12 |
<!-- Android --->
|
13 |
<!-- http://pmd.sourceforge.net/pmd-4.3.0/rules/android.html -->
|
14 |
<rule ref="rulesets/java/android.xml"/> |
15 |
|
16 |
<!-- Design -->
|
17 |
<!-- http://pmd.sourceforge.net/pmd-4.3.0/rules/design.html -->
|
18 |
<rule ref="rulesets/java/design.xml"> |
19 |
<exclude name="UncommentedEmptyMethod"/> |
20 |
</rule>
|
21 |
|
22 |
<!-- Naming -->
|
23 |
<!-- http://pmd.sourceforge.net/pmd-4.3.0/rules/naming.html -->
|
24 |
<rule ref="rulesets/java/naming.xml/ShortClassName"> |
25 |
<properties>
|
26 |
<property name="minimum" value="3"/> |
27 |
</properties>
|
28 |
</rule>
|
29 |
<!-- other rules ignored for brevity -->
|
30 |
</ruleset>
|
Al igual que hicimos con Checkstyle, también necesitamos crear una tarea PMD Gradle para que la comprobación se ejecute dentro del archivo quality.gradle.
1 |
apply plugin: 'pmd' |
2 |
|
3 |
task pmd(type: Pmd) { |
4 |
description 'Run PMD' |
5 |
group 'verification' |
6 |
ruleSetFiles = files("./code_quality_tools/pmd.xml") |
7 |
source 'src' |
8 |
include '**/*.java' |
9 |
exclude '**/gen/**' |
10 |
reports { |
11 |
xml.enabled = false |
12 |
html.enabled = true |
13 |
}
|
14 |
|
15 |
ignoreFailures = false |
16 |
}
|
PMD también está disponible como un plugin de Gradle.
Las propiedades clave de la tarea que hemos creado son:
- ruleSetFiles: Los archivos de conjuntos de reglas personalizados que se utilizarán.
- source: La fuente para esta tarea.
- reports: Los informes que debe generar esta tarea.
Por último, puedes ejecutar el script de Gradle visitando la ventana de la herramienta Gradle, abriendo la carpeta del grupo de verificación y haciendo clic en pmd para ejecutar la tarea. O puedes ejecutarlo a través de la línea de comandos:
1 |
gradle pmd |
También se generará un informe después de la ejecución de la tarea que está disponible en el módulo de aplicación > build > reports > pmd. También hay un plugin de PMD disponible para IntelliJ o Android Studio para que lo descargue e integre si lo desea.
FindBugs
FindBugs es otra herramienta gratuita de análisis estático que analiza tu clase en busca de posibles problemas comprobando tus bytecodes con una lista conocida de patrones de errores. Algunos de ellos son:
- La clase define hashCode() pero no equals(): Una clase implementa el método hashCode() pero no equals(), por lo que dos instancias pueden ser iguales pero no tener los mismos códigos hash. Esto entra en la categoría de malas prácticas.
- Comparación incorrecta de un valor int con una constante long: El código está comparando un valor int con una constante long que está fuera del rango de valores que se pueden representar como un valor int. Esta comparación es vacía y posiblemente dará un resultado inesperado. Esto entra en la categoría de corrección.
-
TestCase no tiene pruebas: la clase es un JUnit
TestCase
pero no ha implementado ningún método de prueba. Este patrón también se encuentra en la categoría de corrección.
FindBugs es un proyecto de código abierto, por lo que puedes ver, contribuir o supervisar el progreso del código fuente en GitHub.
En el archivo findbugs-exclude.xml, queremos evitar que FindBugs escanee algunas clases (usando expresiones regulares) en nuestros proyectos, como las clases de recursos autogeneradas y las clases de manifiesto autogeneradas. Además, si utilizas Dagger, queremos que FindBugs no compruebe las clases Dagger generadas. También podemos decirle a FindBugs que ignore algunas reglas si queremos.
1 |
<FindBugsFilter>
|
2 |
<!-- Do not check auto-generated resources classes -->
|
3 |
<Match>
|
4 |
<Class name="~.*R\$.*"/> |
5 |
</Match>
|
6 |
|
7 |
<!-- Do not check auto-generated manifest classes -->
|
8 |
<Match>
|
9 |
<Class name="~.*Manifest\$.*"/> |
10 |
</Match>
|
11 |
|
12 |
<!-- Do not check auto-generated classes (Dagger puts $ into class names) -->
|
13 |
<Match>
|
14 |
<Class name="~.*Dagger*.*"/> |
15 |
</Match>
|
16 |
|
17 |
<!-- http://findbugs.sourceforge.net/bugDescriptions.html#ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD-->
|
18 |
<Match>
|
19 |
<Bug pattern="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" /> |
20 |
</Match>
|
21 |
</FindBugsFilter>
|
Y por último, incluiremos la tarea findbugs en quality.gradle:
1 |
apply plugin: 'findbugs' |
2 |
|
3 |
task findbugs(type: FindBugs) { |
4 |
description 'Run findbugs' |
5 |
group 'verification' |
6 |
classes = files("$project.buildDir/intermediates/classes") |
7 |
source 'src' |
8 |
classpath = files() |
9 |
effort 'max' |
10 |
reportLevel = "high" |
11 |
excludeFilter file('./code_quality_tools/findbugs-exclude.xml') |
12 |
reports { |
13 |
xml.enabled = false |
14 |
html.enabled = true |
15 |
}
|
16 |
ignoreFailures = false |
17 |
}
|
En la primera línea anterior, aplicamos FindBugs como un plugin de Gradle y luego creamos una tarea llamada findbugs
. Las propiedades clave de la tarea findbugs
que realmente nos interesan son:
-
classes
: las clases que se van a analizar. -
effort
: el nivel de esfuerzo de análisis. El valor especificado debe ser uno de los siguientes:min
,default
, omax
. Debes tener en cuenta que los niveles más altos aumentan la precisión y encuentran más errores a costa del tiempo de ejecución y el consumo de memoria. -
reportLevel
: el umbral de prioridad para informar de los errores. Si se establece como bajo, se reportan todos los errores. Si se establece en medio (el valor por defecto), se reportan los errores de prioridad media y alta. Si se establece en alto, solo se reportan los errores de alta prioridad. -
excludeFilter
: el nombre de archivo de un filtro que especifica los errores que hay que excluir de ser reportados, y que ya hemos creado.
A continuación, puedes ejecutar el script de Gradle visitando la ventana de la herramienta Gradle, abriendo la carpeta del grupo de verificación y haciendo clic en findbugs para ejecutar la tarea. O lanzarlo desde la línea de comandos:
1 |
gradle findbugs |
También se generará un informe cuando la tarea haya terminado de ejecutarse. Estará disponible en el app module > build > reports > findbugs. El plugin FindBugs es otro plugin disponible gratuitamente para su descarga e integración con IntelliJ IDEA o Android Studio.
Android Lint
Lint es otra herramienta de análisis de código, pero ésta viene con Android Studio por defecto. Comprueba los archivos fuente de tu proyecto Android en busca de posibles errores y optimizaciones de corrección, seguridad, rendimiento, usabilidad, accesibilidad e internacionalización.
Para configurar Lint, tienes que incluir el bloque lintOptions {}
en tu archivo build.gradle a nivel de módulo:
1 |
lintOptions { |
2 |
abortOnError false |
3 |
quiet true |
4 |
lintConfig file('./code_quality_tools/lint.xml') |
5 |
}
|
Las opciones de Lint clave que nos preocupan son:
-
abortOnError
: si lint debe establecer el código de salida del proceso si se encuentran errores. -
quiet
: si se desactiva el informe de progreso del análisis. -
lintConfig
: el archivo de configuración por defecto a utilizar.
Tu archivo lint.xml puede incluir los problemas que quieres que Lint ignore o modifique, como el ejemplo siguiente:
1 |
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
<lint>
|
3 |
<!-- Disable the given check in this project -->
|
4 |
<issue id="IconMissingDensityFolder" severity="ignore" /> |
5 |
|
6 |
<!-- Change the severity of hardcoded strings to "error" -->
|
7 |
<issue id="HardcodedText" severity="error" /> |
8 |
</lint>
|
Puedes ejecutar Lint manualmente desde Android Studio haciendo clic en el menú Analizar, eligiendo Inspeccionar Código... (el alcance de la inspección es todo el proyecto), y luego haciendo clic en el botón OK para proceder.






También puedes ejecutar Lint visitando la ventana de herramientas de Gradle, abriendo el grupo de verificación y haciendo clic en lint. Por último, puedes ejecutarlo a través de la línea de comandos.
En Windows:
1 |
gradlew lint |
En Linux o Mac:
1 |
./gradlew lint |
También se generará un informe cuando la tarea haya terminado de ejecutarse, que está disponible en app module > build > outputs > lint-results.html.
Bonificación: StrictMode
StrictMode es una herramienta para desarrolladores que ayuda a evitar que los desarrolladores de tu proyecto realicen cualquier E/S de flash o de red accidental en el hilo principal, ya que esto puede hacer que la aplicación sea lenta o no responda. También ayuda a evitar que aparezcan diálogos ANR (App Not Responding). Una vez corregidos los problemas de StrictMode, tu aplicación será más receptiva y el usuario disfrutará de una experiencia más fluida. StrictMode utiliza dos conjuntos de políticas para aplicar sus reglas:
-
Políticas VM: protege contra las malas prácticas de codificación como no cerrar los objetos
SQLiteCursor
o cualquier objetoCloseable
que se haya creado. - Políticas de hilos: busca que operaciones como la E/S de la flash y la E/S de la red se realicen en el hilo principal de la aplicación en lugar de en un hilo de fondo.
1 |
if (BuildConfig.DEBUG) { |
2 |
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() |
3 |
.detectDiskReads() |
4 |
.detectDiskWrites() |
5 |
.detectNetwork() // or .detectAll() for all detectable problems |
6 |
.penaltyLog() // Log detected violations to the system log. |
7 |
.build()); |
8 |
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() |
9 |
.detectLeakedSqlLiteObjects() |
10 |
.detectLeakedClosableObjects() |
11 |
.penaltyLog() |
12 |
.penaltyDeath() // Crashes the whole process on violation. |
13 |
.build()); |
14 |
}
|
El código anterior puede estar en el método onCreate()
de tu aplicación, actividad u otro componente de la aplicación.
Puedes aprender más sobre StrictMode
aquí en Envato Tuts+.
Un ejemplo de proyecto Android que implementa todo lo anterior, incluyendo conjuntos de reglas de las herramientas para un proyecto típico de Android, se puede encontrar en el repositorio GitHub de este post.
Conclusión
En este tutorial, aprendiste cómo asegurar un código Android de alta calidad usando herramientas de análisis de código estático: qué son, beneficios de usarlas y cómo usar Checkstyle, FindBugs, Lint, PMD y StrictMode en tu aplicación. Anímate y prueba estas herramientas, puede que descubras algunos problemas en tu código que no esperabas.
Mientras tanto, ¡echa un vistazo a algunos de nuestros otros cursos y tutoriales sobre el desarrollo de aplicaciones para Android!
-
Android SDKRxJava 2 para aplicaciones Android: RxBinding y RxLifecycleJessica Thornsby
-
Android StudioCodificación de aplicaciones funcionales para Android en Kotlin: Cómo empezarJessica Thornsby
-
Android SDKAndroid From Scratch: Usando las APIs de RESTAshraff Hathibelagal
-
Android SDKCómo crear una aplicación de chat para Android usando FirebaseAshraff Hathibelagal