Tinselcity

Recientemente, en uno de los peores trabajos/proyectos en los que he estado, me he dado cuenta de algo: Demasiados programadores intentan aprender cómo hacer las cosas sin aprender por qué hacerlas de esa forma. Buscan recetas de cocina, en lugar de aprender a cocinar.

¿Por qué hacemos lo que hacemos?

Pasados los primeros meses de desconfianza, últimamente algunos ya empiezan a pedirme ayuda de forma más o menos regular.

Probando, probando...

Esta semana pasada he estado solucionando problemas con Leonard1) en varias ocasiones. Se acerca y me dice:

¿Tienes un minuto?

Y voy con él y me enseña su problema. Los problemas son variados, en complejidad y en dificultad. A veces resulta ser literalmente un minuto. A veces pueden ser 30 o más. Cuando no es Leonard, es Marjorie, Francis o Stephen.

Mientras resolvemos el problema de turno, intento hacer que no sólo se queden con una solución sino que también se lleven algo más, que aprendan algo. Y cuando terminamos y me vuelvo a mi sitio, antes de seguir con lo mío, intento hacer una pequeña reflexión de cómo ha ido. Esto me ha permitido darme cuenta de algunas cosas. Hay un intercambio de palabras que ha estado ocurriendo frecuentemente y de forma muy similar.

Nos ponemos a ver algún trozo de código y veo algo que no entiendo por qué está ahí.

¿Esto de aquí por qué lo haces?

Ah, no lo sé; eso lo he visto en otro sitio que lo hacían así.

Y esto otro que le está pasando X, ¿no debería pasarse Y? ¿Por qué está así?

No sé, he estado cambiando cosas a ver si lo conseguía arreglar.

Este tipo de respuesta (“no sé por qué lo hago, pero pruebo a ver si funciona”) es más intenso con Leonard, pero es bastante general con todo el equipo. Se debe a toda una serie de circunstancias e historias que darían para escribir miles de palabras. Pero es otro tema.

Envoltorios con agujeros

Otra cosa que ocurre según voy pasando tiempo aquí es que al llegar veía caos y confusión y pensaba que era todo un desastre, pero ahora voy encontrando los pequeños detalles, los orígenes de los problemas y que es todo un desastre. Pero además de esa confirmación que sirve de poco consuelo, esto me proporciona una visión más precisa de cómo es ese desastre.

Hace unos días discutía… Bueno, no, evito completamente discutir con el Arquitecto porque es bastante inútil, así que digamos que le escuchaba evitando expresar lo que realmente estaba pensando. Me decía que no se debía llamar a cierto método sino a otro, que para algo se había hecho cierto widget de jQueryUI.

El proyecto está basado en una cierta librería de componentes de interfaz. Hace alrededor de unos 10 años se eligió esta librería y se decidió usarla como base para todo el interfaz web del proyecto. La librería es probablemente una de las peores elecciones que se podían hacer, pero eso ya es irrelevante a estas alturas. A la vez, aproximadamente, que se empezaba a utilizar esta librería, se desarrollaban una serie de envoltorios sobre ella en forma de widgets de jQueryUI.

Cuando hacemos un envoltorio tenemos, en general, un objetivo: aislarnos del componente original. Lo podemos hacer porque pensemos en un futuro en que este componente pueda ser actualizado sin afectar a nada más allá del envoltorio, o lo podemos hacer porque queremos ocultar una gran complejidad y que el envoltorio aporte toda una serie de decisiones que deberían ser siempre iguales en nuestro proyecto. En cualquier caso queremos aislarnos del componente original.

Según voy teniendo tiempo para revisar estos envoltorios me voy dando cuenta de que en todos o casi todos ellos ocurre lo mismo. No he tenido suficiente tiempo de verificar si el proceso es igual en todos los casos, pero en unos cuantos ha ocurrido así: El widget de jQUI empieza como un envoltorio razonable. Tiene una serie de añadidos, mejoras o adaptaciones sobre lo que ofrece el componente original. Poco a poco, va añadiendo más opciones, que dan acceso a más funcionalidades del original. La complejidad del envoltorio va creciendo y en un momento dado alguien considera que es más cómodo añadir getOriginalObj, un método que directamente te ofrece acceso al componente original para que hagas lo que necesites. A partir de ahí, el envoltorio empieza a dejar huecos sin arreglar porque, de todos modos, tienes acceso al componente original si quieres hacer esa otra cosa.

Es decir, para casi todos los envoltorios que me encuentro ocurre que, en algún momento, pierden su objetivo, dejan de hacer aquello para lo que se habían creado. Ni nos simplifican la complejidad ni tampoco nos aíslan. Es más, lo que hacen ahora es producir una complejidad adicional ya que es necesario saber si cierta acción debe realizarse a través del envoltorio o directamente sobre el componente original. Y así, el código de todo el proyecto es una horrible mezcla de ambas cosas.

    // Esto se hace a través del envoltorio porque carga los datos
    // de una respuesta JSON. Si fuera XML se haría loadXML sobre el original
    $articulos.listado('cargarDatos', ...);
    
    // Esto se hace con el componente original
    $articulos.listado('getOriginalObj').selectRow(...);
    // Esto desde el envoltorio
    $articulos.listado('getSeleccion');

Mucho trabajo; ¿poca recompensa?

Algo antes de cerrar mi cuenta de Reddit, recuerdo que alguien exponía en /r/javascript su cansancio al hacer import { esto, eso, aquello } from librería; y tener que añadir cada cosa que usaba frente a empezar siempre con un import * from librería; desde el principio y olvidarse de más preocupaciones.

Le recordé2) que una ventaja es que hacer ese esfuerzo de tener que escribir explícitamente cada cosa que usa, es un recordatorio de todo lo que está usando y que le debería ayudar a pensar en si, quizá, está haciendo demasiadas cosas y ese módulo es demasiado grande o demasiado complejo o ambos.

La ganancia a veces es sutil, no siempre es algo enorme o evidente. La alternativa tiende a ser mucho más palpable y, sobre todo, inmediata. A largo plazo, sin embargo, casi siempre compensa.

La tentación vive dentro de ti

Pensando en estas cosas, creo que lo que hay detrás de todo esto es algo mucho más general de lo que nos gustaría admitir. No es nada nuevo. Me preocuparía pensar que algún programador no ha oído estas cosas alguna vez en su vida.

Todos sabemos cómo hacer “bien” las cosas. Hay miles de charlas de gente que habla de cómo de bien hacen algo en su proyecto o empresa. En cualquier entrevista de trabajo que hagas te contarán lo mucho que valoran la calidad y el hacer las cosas “bien”. Y si surge la ocasión de hablar de algo que se está haciendo mal en algún sitio3), cualquier programador se rasgará las vestiduras señalando lo evidente que es que las cosas deben hacerse bien.

Y sin embargo es raro hasta el extremo, el programador que no ha caído alguna vez en esa pequeña tentación de sacrificar la corrección por un poco de comodidad. Agujereamos un envoltorio porque es más cómodo así. O simplemente copiamos y pegamos porque es más rápido. O probamos a arreglar problemas a base de pulsar combinaciones de botones porque no requiere el esfuerzo de entender el problema. O usamos esa otra sintaxis más simple sin siquiera planteárnoslo.

Que sí, que esto no siempre es tan malo o desastroso. Yo controlo, ya, sí. Pero demasiadas veces me he encontrado que se cae en la tentación porque, simplemente, se ignora por completo por qué se estaba haciendo ese esfuerzo, por qué se hace de esa manera y no de esta otra más cómoda.

Y ahí está el detalle. No tanto en si sacrificamos algo o si nos saltamos algún tipo de regla4), sino en que demasiadas veces perdemos de vista el por qué de las cosas. En ese momento ya hemos perdido, hagamos las cosas bien o mal. En el momento en que dejamos de saber por qué estamos haciendo algo, pierde el sentido hacerlo.

Hazlo o no lo hagas... pero así de entrada no lo hagas

El caso que me ocupa actualmente, con el proyecto desastroso, es muy extremo. Pero este es el consejo que intento aplicar y explicar siempre.

Puede que tengas que hacer esto o que no tengas que hacerlo. Pero, por defecto, mientras no sepas que tienes que hacerlo, no lo hagas.

Y ojo, que esto no significa que no puedas, en un momento dado, “probar a ver si es esto funciona”. Lo que significa es que si haces algo debes tener una idea razonable de por qué haces eso y que si no la tienes, debes buscarla. Es más, si pruebas a ver una solución, tanto si finalmente funciona como si no lo hace, deberías investigar por qué.

Y, ¿por qué eso de por defecto no hacerlo? Porque ayuda a limitar la confusión y el caos. Cuantas menos cosas hagamos en general, menos complejidad tenemos. Pero además, en este caso, es una complejidad particularmente peligrosa porque estamos añadiendo cosas que sabemos que no sabemos. Es decir, somos conscientes de que estamos introduciendo posibilidades de errores que no vamos a entender ni saber arreglar.

Como segunda idea a aplicar: Una vez que sepas por qué y decidas que, efectivamente, debes hacerlo, ten siempre presente ese porqué. Si lo olvidas, entonces perderá de nuevo el sentido.

Esto es aplicable en general a muchas cosas. Y seguramente, como todo, tiene limitaciones y excepciones y pequeños detalles que considerar, claro. Pero, en mi opinión, entender por qué hacemos las cosas de una cierta manera aporta fácilmente alrededor del 90% de las soluciones.

Lógicamente, en un proyecto típico esta norma, aunque fundamental, no es suficiente. No lo es porque en un proyecto típico no somos los únicos que escriben el código, ni podemos conocer con todo detalle toda la extensión del proyecto. Así que, incluso siguiendo la norma, ocurrirá que nos encontremos frente a un código que debemos modificar o en el que debemos solucionar un problema, y en el que existe una fracción más pequeña o más grande, de partes que no conocemos.

Don't panic; I can explain

Nueva situación. Hemos hecho lo posible por contener lo desconocido y, sin embargo, nos encontramos con un problema y, vaya, hay cosas que no conocemos, hay partes del código que no sabemos qué hacen o por qué están ahí.

Una reacción que no deja de desconcertarme, a pesar de lo frecuentemente que la veo, es la que comentaba antes: Empezar a pulsar botones, a probar cosas al azar, sin más, para ver qué pasa. Además, por lo que veo casi siempre, esto se produce durante un par de horas, acumulando pequeños cambios aquí y allá, en los alrededores del problema (o en sitios que no tienen relación alguna). Cuando esto ocurre y alguien me pide ayuda después de esas horas, lo que termino viendo casi siempre es que el código está en un estado muy confuso. Han acumulado toda una serie de pequeños cambios, algunos relevantes, otros no. Algunos no tienen ningún efecto en absoluto, otros estropean otras cosas que antes funcionaban bien, con algo de suerte otros arreglan otras cosas que no se habían detectado hasta el momento5). Es todo bastante aleatorio y desordenado.

El problema con esto es múltiple. No sólo produce, a nivel personal, frustración y confusión creciente, este proceso lo único que hace es añadir cada vez más complejidad al problema. ¿Cómo afectará cada combinación particular de las 8 cosas que hemos cambiado? Si cada cambio tiene digamos 3 formas o variantes, eso son 38, algo más de 6500 combinaciones a probar. Lógicamente este no es un buen camino para llegar a ningún sitio. Así que como punto de partida: calma.

A partir de ahí, la forma de avanzar es la de siempre. Primero entender, luego resolver. Es decir, solo hacer modificaciones de aquellas cosas que entendemos, que sabemos para qué son. Cuando encontramos cosas que no sabemos para qué son, primero averiguamos para qué son y eso mismo ya nos dirá si tiene sentido cambiarlo o no.

Sin conocer el por qué de las cosas, incluso aunque acertáramos de casualidad, nunca estaremos seguros de que realmente lo hemos hecho bien / como queríamos.

1)
Como es costumbre: The names have been changed to protect the innocent
2)
inútilmente, porque a nadie le interesa el argumento de que el esfuerzo tiene compensaciones
3)
preferiblemente en otro sitio, claro
4)
que a veces es conveniente, sí
5)
Eso no suele pasar :p