Por qué Java es malo para tu mente

Autor: | Última modificación: 13 de noviembre de 2022 | Tiempo de Lectura: 4 minutos
Temas en este post:

En mis cursos de iOS, casi siempre tengo que introducir Objective C a programadores que jamás antes han usado un lenguaje dinámico. A menudo, provienen de Java, C# o C++.

Siempre que lo hago, no puedo dejar de recordar el poema «La Pantera» de Rilke:

Su mirada, cansada de ver pasar
las rejas, ya no retiene nada más.
Cree que el mundo está hecho
de miles de rejas y, más allá, la nada.

black-panther-in-beijing-zoo-pic-rex_Java es malo
Miles de rejas, y más allá, la nada.

El tipado estático: la mayor optimización prematura inventada por el hombre

En el pasado, he usado varios lenguajes con tipado estático, especialmente C++. Lo dejé por imposible cuando finalmente me percaté que el tipado estático es la mayor optimización prematura jamás creada por el hombre.

Los tests que puede hacer el compilador son en general de escasa utilidad y no detectan los errores serios en los que uno pueda incurrir, básicamente porque el compilador no tiene conocimiento del dominio de la aplicación, y terminan más siendo un estorbo que una ayuda.

Por ejemplo, si estás haciendo una aplicación de conversión de divisas, tendrás que estar pendiente de no usar por error un entero, porque el compilador te pegará un grito diciendo que tendría que haber sido un double. Ahora bien, ¿qué importancia tiene eso?

Lo que de verdad importa es si las funciones de conversión entre divisas funcionan correctamente y no detalles de bajo nivel. Este tipo de seguridad, la que tiene en cuenta el dominio de tu aplicación, no te la puede dar ningún compilador. Para esto necesitas conocimiento de la semántica de tu aplicación, y eso sólo lo puedes hacer tú con una batería de tests unitarios y la práctica del TDD.

Es decir, la «seguridad» que te aporta un compilador estricto no es tal, ya que al carecer de contenido semántico, sus tests son de escaso o nulo valor. Sin embargo, no son inocuos, ya que para crear cualquier tipo de aplicación mínimamente compleja se necesita el tipo de cosas que un lenguaje dinámico permite de entrada, como colecciones que aceptan más de un tipo de objeto.

Al no poder hacerlo por las buenas, se hará por las bravas: toma genéricos y templates (en C++).

Si nos fijamos en el uso «avanzado» de lenguajes furibundamente estrictos, como puedan ser C++ o Haskell, básicamente se trata de emular un lenguaje dinámico pero con una complejidad infinitamente mayor: las locuras que se llegan a hacer en C++ con los templates o las pajas mentales portentosas que se hacen los haskelitas con sus mónadas y sus gónadas.

Patrones de diseño: una señal de ineficiencia

No sólo te ves obligado a crear una ontología de clases e interfaces (en el sentido de Java) sumamente compleja para esquivar las limitaciones del compilador, sino que tienes que inventarte mecanismos innecesariamente complejos para llevar a cabo tareas comunes y triviales; es decir, los patrones de diseño.

Por ejemplo, como en C++ no se pueden añadir métodos a un objeto en tiempo de ejecución, pues toca inventarse el patrón del Visitante. Como tampoco le puedes cambiar la clase a un objeto en tiempo de ejecución, pues te inventas otro rodeo absurdo: el patrón del composite.

Cuanto más populares y usados son los «patrones de diseño» en una comunidad, más limitado e inflexible es su lenguaje.

C++, el carcelero mayor

El caso de C++ es especialmente hiriente ya que su complejidad es tal que malgasta el ancho de banda mental de cualquier desarrollador. Cuando descubrí Lisp (algo así como «cuando vi a Jesús» 😉 me encontraba en la cumbre de mi uso de C++. Me creía la rehostia, hacía cosas rarísimas con los templates e incluso precalculaba cosas en tiempo de compilación con templates recursivos.

Acababa de salir este libro que era la biblia de los desarrolladores «avanzados» de C++ y por supuesto lo compré….y no volví a tocar C++ (o cualquier otro lenguaje estático) en mi vida. Ese tocho, de una complejidad acojonante, no es más que formas de hacer aquello que es trivial con Lisp (o cualquier lenguaje dinámico) de forma inhumanamente complicada. Sentí lástima de mi, de mis malgastadas neuronas y de todos los demás galeotes de C++.

Si al final vamos a terminar haciendo lo mismo (independientemente del lenguaje), ¿por qué insistir en usar una herramienta que lo hace innecesariamente difícil?

Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.

— Greenspun’s 10th law of programming

Los lenguajes dinámicos son lentos

El argumento del rendimiento es en el fondo falso. Si bien es cierto que las comprobaciones que un lenguaje dinámico hace en tiempo de ejecución pueden tener un cierto coste (mínimo en el caso de Objective C), es un error pensar que se trata de una buena inversión usar un leguaje estático para ahorrarse es coste.

El cuello de botella nunca está en el código.

El cuello de botella eres tú.

¿Qué importa que tu código sea unos nanosegundos más rápido que el de la competencia, si mientras tú todavía estás declarando variables, ellos ya han sacado su producto al mercado? Nadie ha explicado esto mejor, y demostrado de forma fehaciente, que Paul Graham en su ensayo «Beating the averages«.

La mejor optimización de todas

Es decir, el tipado estático es una optimización prematura, ya que estás optimizando algo que no es tu cuello de botella. La mejor optimización es aquella que mejora la productividad del desarrollador, y esa, es la libertad para probar rápidamente nuevos conceptos o algoritmos, explorar el problema y el dominio de la aplicación en vez de estar atado de pies y manos a una absurda y estricta jerarquía de tipos.

Y es que

«La libertad, Sancho, es uno de los más preciosos dones que a los hombres dieron los cielos; con ella no pueden igualarse los tesoros que encierran la tierra y el mar: por la libertad, así como por la honra, se puede y debe aventurar la vida.»

[email protected]