domingo, 28 de diciembre de 2014

Cuando la explicación correcta es la obvia pero no lo parece

Un mal día


Un día como otro cualquiera estaba en la oficina programando en Java, utilizando una versión del Netbeans que había actualizado recientemente.

Tengo por costumbre utilizar muy a menudo el botón Clean and build (Limpiar y generar) porque estoy harto de que me pasen cosas raras con versiones desfasadas de ficheros compilados.

En una de tantas veces, recompilé y arranqué el programa. Al llegar a la pestaña en la que estaba trabajando me saltó un error NoClassDefFoundError indicando que no podía encontrarse una inner class que tenía como oyente de un botón. La documentación de dicho error dice:

Thrown if the Java Virtual Machine or a ClassLoader instance tries to load in the definition of a class (as part of a normal method call or as part of creating a new instance using the new expression) and no definition of the class could be found.
The searched-for class definition existed when the currently executing class was compiled, but the definition can no longer be found.

En ese momento me dije: eso no puede ser, porque si acabo de compilar sin errores de compilación, y dicha clase es parte del programa, la clase existe y es correcta. Y yo no manipulo para nada el class-path.

Fue uno de esos momentos en los que piensas que el IDE se ha corrompido. Cerré y reabrí el Netbeans, pero seguía fallando. Limpié la caché, pero más de lo mismo. Y pensé, ¿acaso no estará compilando esta clase por alguna extraña razón?
  • ¿el nombre de la inner class será demasiado largo?
  • ¿se me habrá colado algún caracter extraño y habrá problemas de encoding?
Hice pruebas como renombrar la clase, revisar el encoding del fichero y cosas así, y en una de tantas, funcionó y me olvidé del tema. Una de esas veces que te dices: ¡A saber lo que estaría pasando!.

Pero al siguiente día, el mismo error. Y esta vez pasaba el tiempo y era incapaz de conseguir solventarlo. Así que me dije: ¿puede ser que ese error se produzca por alguna otra razón? ¿Tal vez alguna excepción previa esté siendo silenciada?

Y así descubrí que ese error se puede producir si en algún trozo de código static se produce algún ExceptionInInitializerError que impida cargar la clase. O sea, en ese caso el error debería llamarse ClassLoadingError o algo similar, pero se reutiliza la misma clase Throwable.

Me cercioré que no había ningún ExceptionInInitializerError en el stack trace, incluso desactivé el UncaughtExceptionHandler que utilizo por si yo mismo estaba silenciando ese error. Pero nada.

Y lo peor de todo, es que esporádicamente, conseguía evitar el Runtime Error. Y no dejaba de preguntarme: ¿Cómo es posible? ¿Qué he cambiado? ¿Por qué unas veces sí y otras no?

Como diría Sherlock Holmes


Como diría Sherlock Holmes: "una vez descartado lo imposible, lo que quede, por improbable que parezca, tiene que ser la verdad".

Tras el enésimo Clean and build busqué el fichero ".class" en el subdirectorio build del proyecto, y efectivamente no estaba. Abrí el fichero JAR resultante con un manejador de ficheros comprimido, y efectivamente, allí tampoco estaba. Conclusión: ¡el error se produce porque la clase no existe!

Yo no manipulaba el class-path, pero por alguna razón, el fichero ".class" no estaba donde debería estar.

Esta vez, probé a compilar únicamente el fichero comflictivo y apareció el fichero ".class". En esas condiciones, funcionaba la aplicación. Sin embargo, tras un Build desaparecía la clase.

Finalmente, tras ponerle un nombre totalmente diferente a la inner class, todo fue como la seda. Con la moral por los suelos y la cabeza a punto de estallar, me dije que sería algún fallo estrambótico del ant con el nombre que le tenía puesto a la inner class.

Pero al contarle la batallita a un compañero me hizo un comentario que al fin, me abrió los ojos. Hacía mucho tiempo, había modificado el fichero build.xml para borrar del subdirectorio build (y por tanto, no incluir en el JAR) todo lo que estuviese por debajo un package llamado xtra. Al revisar la regla, me dí cuenta de que se me había colado un * de más en el patrón. Como el nombre que usaba para la inner class contenía la cadena Extra, encajaba en el erróneo patrón y su fichero ".class" era borrado.

Conclusión


El error lanzado por la JavaVM siempre me había indicado correctamente lo que estaba pasando y la culpa, en el fondo, siempre había sido mía. La explicación correcta siempre fue la obvia, pero me parecía tan imposible que lo fuese, que removí cielo y tierra para buscar otra explicación.

Greer's Third Law: A computer program does what you tell it to do, not what you want it to do. ¿Anónimo?

If you don't know anything about computers, just remember that they are machines that do exactly what you tell them but often surprise you in the result. Richard Dawkins, The Blind Watchmaker (1986)

Y como no hay mal que por bien no venga, además de lo que aprendí, corregí el fichero build.xml que era donde realmente estaba el problema.

Enlaces


Enlaces de interés relacionados con este artículo:

(Actualizado 29/12/2014)