Pensá antes de usar BuildConfig.DEBUG

Facundo Rodríguez Arceri
Droid LATAM
Published in
4 min readFeb 28, 2020
Photo by Hack Capital on Unsplash

La semana pasada me encontraba trabajando en una funcionalidad que se encargaba de sincronizar información entre el dispositivo y el backend. Este mecanismo de sincronización es disparado bajo ciertas condiciones y lo que necesitábamos era tener una forma de saltearnos esas condiciones y forzar el proceso de sincronización al habilitar cierta preferencia disponible sólo cuando estamos en modo debug. Dejando de lado los detalles, el código resultante era algo como lo siguiente:

Bastante simple. Utilicé como condición la constanteBuildConfig.DEBUGque genera Android Studio.

El problema

Todo parecía estar bien para mí hasta que recibí un comentario en el pull request correspondiente:

No quisiera incluir funcionalidad de Debug junto con nuestro código productivo dentro del APK/Bundle.

Estoy de acuerdo con esto, y hay varias razones para ello:

  • ⛔ Definitivamente no es una buena práctica.
  • 🔒 Puede resultar en problemas de seguridad: ¿Qué pasa si alguien decompila el artefacto generado y encuentra que estás haciendo trucos para saltearte validaciones? ¿Qué pasaría si logran replicar ese comportamiento? Edit: Si usas Proguard/R8 esto no debería ser una preocupación, ya que las optimizaciones que realiza el compilador hará que sea imposible ver la condición utilizada. De todas formas recordá que estas herramientas no vienen habilitadas al crear un nuevo proyecto y deberías ocuparte de ello.
  • 🐞 ¿Y si hubiera un bug en la generación de la clase BuildConfig? Tal vez ni siquiera lo consideraste, pero tengo que contarte que hace algunos años había un bug no resuelto que causaba que BuildConfig.DEBUG retornara siempre true, incluso en el .apk generado.

Hay mejores alternativas a esto…

… pero la correcta depende de lo que estés intentando hacer.

Publiqué un tweet con una encuesta y gracias a las respuestas pude aprender bastante sobre la forma en la que los desarrolladores utilizamos esta constante, y cuándo y por qué estamos haciendo las cosas de forma probablemente errónea.

Incluir funcionalidad sólo disponible en debug

Este es mi escenario, pero recibí algunas respuestas de otros desarrolladores que tienen un problema similar, por ejemplo agregar Interceptors a los requests HTTP de la aplicación para poder hacer logging. Deberíamos evitar hacer esto y mi recomendación es utilizar el poder de los flavors para poder separar la lógica de debug de la lógica productiva, incluyendo la misma clase en los flavors de release y debug. En mi caso particular, el resultado está incluido en los siguientes fragmentos de código.

La funciónsyncData original debería ser tan simple como:

Magia. No hay ningún condicional que dependa del ambiente. Ahora estamos delegando la condición a un objeto llamado SyncCoordinator, veamos cómo deberíamos implementarlo:

Nótese que estos objetos tienen el mismo nombre y que están, obviamente, en dos archivos diferentes. La clave es la ruta del archivo: el primero está bajo el directoriosrc/release y el segundo ensrc/debug. El sistema de build va a utilizar sólo una de estas implementaciones dependiendo de cuál sea el build variant activo. Cuidado: no es posible tener una clase base y luego intentar sobre-escribirla en cada flavor. Podes encontrar más información sobre esto en esta pregunta de StackOverflow.

También habrás notado que esta forma de separar las cosas nos permite, también, escribir los tests en forma separada para cada flavor, y así poder mantener los tests dedebug de forma independiente a los de release, lo cual es mucho más limpio en el código. Las clases que contengan estos tests deberían estar en los siguientes directorios: MyProject/src/testDebug/java/com/facundomr/example/util/SyncCoordinatorTest.kt y MyProject/src/testRelease/java/com/facundomr/example/util/SyncCoordinatorTest.kt .

Ahora que resolvimos esto podemos avanzar y evaluar otras situaciones posibles:

Cambiar un valor/constante dependiendo del flavor

Si sólo querés definir un valor distinto entonces no tenés que escribir ningún código extra. Podés definir el mismo buildConfigField en cada flavor y eso es suficiente. Para más información sobre esto seguí la documentación oficial.

Incluir librerías que sólo se necesitan cuando estás en modo debug

Si estás haciendo esto, probablemente no sólo estás incluyendo funcionalidad de debug en tu .apk productivo sino que probablemente estés incrementando significativamente el tamaño y la cantidad de métodos (method count) de tu aplicación. Para evitar esto te recomiendo leer el siguiente artículo sobre No-op versions for dev tools.

Evitar que se ejecute cierto código en producción

La diferencia es sutil: no es lo mismo incluir funcionalidad de debug que evitar que en producción se ejecute algún código en particular: en este caso probablemente no debas tener ninguna preocupación sobre posibles problemas de seguridad si alguien llegara a descubrir que, por ejemplo, estás deshabilitando los logs basándote en una condición tan simple como depender deBuildConfig.DEBUG. De todas formas hay una manera mucho mejor y más elegante de hacer esto: usar Proguard para eliminar esas líneas al momento de exportar tu artefacto. Te recomiendo este artículo de Craig Russell.

¡Cuidado cuando escribís tu app en módulos!

Si estás estructurando tu app en distintos módulos (por ejemplo:app y ui, y estás dependiendo de la constante en el módulo uipara hacer algo particular, entonces tené cuidado: BuildConfig.DEBUG en app no es lo mismo queBuildConfig.DEBUG en el móduloui: podrías estar ejecutando código de debug incluso en el .apk generado. Hay una forma más segura de hacer un chequeo global sobre si la app se está ejecutando en modo debug o no: se llama ApplicationInfo.FLAG_DEBUGGABLE. Podés leer el siguiente artículo para aprender más sobre cómo utilizarla.

Conclusión: ¿Debería siempre evitar el uso de BuildConfig.DEBUG?

Definitivamente no. No estoy diciendo eso. Puede haber situaciones en la no te preocupe nada de lo que menciono en este artículo y está perfectamente bien usar la constante, por ejemplo: deshabilitar Crashlytics en debug. Cuando hagas esto de esa forma:

  • De todas formas vas a incluir la librería en producción.
  • No estás incluyendo funcionalidad de debug.
  • No es peligroso que alguien descubra que estás deshabilitando los reportes de error cuando probás tu propia app.

Mi única conclusión es: deberíamos estar considerando todas estas opciones antes de escribir código que utiliza la constante BuildConfig.DEBUG. Quizás hay una forma mejor, más segura y elegante de lograr lo mismo, y siempre está bueno aprender cosas nuevas.

Happy coding! 😊

--

--