Entraremos en conceptos que, de nuevo, pueden parecer elementales para un profesional, pero que para mi han supuesto el descubrimiento de un vocabulario completamente nuevo, con palabras como "escalabilidad" o "modularización", entre muchas otras.
A pesar de que nos adentramos en conceptos técnicos de cierta enjundia, mi intención es que este no sea, ni mucho menos, un post técnico. En primer lugar, porque no dejo de ser un programador aficionado y soy plenamente consciente de que, en muchos de estos temas, apenas rozo la superficie de su verdadera comprensión. En segundo lugar, y quizás más importante, porque mi objetivo es que cualquier lector ajeno al mundo de la programación pueda seguir este relato sin sentir la tentación de salir corriendo ante una montaña de tecnicismos.
En cualquier caso, vaya por delante que aquí solamente vamos a exponer los conceptos más relevantes y con mayor trascendencia a los que me he tenido que enfrentar, sin perjucio de que los desafíos que aparecen cuando aprendes a programar son interminables.
Modularización: la especialización por bandera
Ahora vamos a imaginar que de tu armario seleccionaras un manual de Derecho Penal. Lo lógico sería que el mismo estuviera organizado siguiendo la propia estructura del Código Penal, diferenciando un primer gran bloque correspondiente al Libro I (parte general) del Libro II (parte especial), y abordando una a una las distintas instituciones penales de forma autónoma pero interconectadas entre sí.
Sin embargo, imagina la sorpresa que sería ver que el contenido es una sucesión de palabras una tras otra, de principio a fin del libro, sin estructura y sin oxígeno. Mezclando la autoría con el delito de estafa, o la prescripción de las penas con la individualización de las mismas. Ese caos absoluto, donde resulta imposible localizar una institución concreta sin leer el resto, es exactamente lo que sucede cuando un programa carece de modularización.
Modularizar, en términos técnicos, no es más que fragmentar un programa complejo en piezas más pequeñas, independientes y especializadas —los módulos—, de modo que cada una se encargue de una tarea única y bien definida, pero actuando en interrelación con las demás.
Esta independencia garantiza que el código sea previsible, ya que cualquier modificación o corrección queda confinada a un solo archivo, evitando que un cambio en una función aislada provoque errores en el resto de la estructura.
La ausencia de modularización fue, precisamente, el primero de los errores en los que incurrí al empezar a programar. Terminé creando archivos masivos de más de 20.000 líneas de código en donde yo, que era su autor, era incapaz de encontrar nada.
Era un entorno caótico donde se mezclaban las funciones de interfaz con las funciones de gestión de datos, las funciones accesorias y con todo lo que se pueda imaginar.
Al igual que en ese manual de derecho sin capítulos, cualquier intento de consulta o mejora se convertía en una odisea, evidenciando que la falta de orden arquitectónico es el mayor enemigo de la eficiencia técnica.
Esta falta de modularización fue la que lastró la primera versión de la herramienta de conformidad penal. En aquel momento, el sistema se sostenía sobre apenas tres archivos masivos que carecían de cualquier tipo de estructura interna.
No existía una división por funciones o materias, sino que la única segmentación era puramente personal: un archivo contenía mi parte del desarrollo y los otros dos la de Alberto. Al no existir una organización basada en la responsabilidad única de cada componente, el código se convirtió en un bloque rígido y difícil de gestionar donde cualquier ajuste técnico o jurídico suponía un riesgo para la estabilidad de todo el conjunto.
Por ello, tras culminar la primera fase de desarrollo y testeo, rápidamente me di cuenta que tocaba abordar una segunda gran fase: la refactorización del código. Refactorizar consiste, básicamente, en reestructurar el código interno sin alterar su comportamiento externo para hacerlo más eficiente y legible.
Mi tarea fue precisamente esa: convertir aquel bloque monolítico inicial en algo estructuralmente estable, saneando las entrañas del programa para que su arquitectura fuera capaz de soportar futuras actualizaciones sin colapsar.
De este proceso de limpieza nació la estructura actual de 24 archivos: un núcleo central, 6 gestores técnicos, 12 módulos jurídicos independientes y diversos archivos de clases y utilidades. Logré que el código dejara de ser una montaña de instrucciones apiladas para convertirse en una arquitectura con perímetros definidos donde cada componente tiene una misión clara.
Pese a ello, soy plenamente consciente de que este diseño todavía dista mucho de la perfección y aún podría hacerse muchísimo mejor, pues la optimización de un software es un camino de aprendizaje que nunca termina.
Os dejo aquí el acceso directo a la entrada donde detallo el funcionamiento de la herramienta de conformidades. Allí podréis observar la estructura real que acabo de describir y cómo esa organización modular permite gestionar un flujo de trabajo complejo.
La experiencia me ha enseñado que lo ideal es, precisamente, no tener que abordar nunca una refactorización, y plantear, de origen, un código modular. La tarea de separar el código ya existente y asegurarse de que sigue funcionando es una de las labores más tediosas y quirúrgicas que he tenido que llevar a cabo.
Un programa se caracteriza por tener miles de líneas de código, con múltiples variables y funciones entrelazadas que, al separarlas, deben ser modificadas o adaptadas al nuevo entorno generado, lo cual es un reto técnico que preferiría no tener que volver a experimentar.
Optimización del código: de la reutilización de funciones
Dicha máxima consiste en aplicar la ley del mínimo esfuerzo para no escribir dos veces lo mismo si puedes evitarlo.
Imagina que quieres hacer cien galletas con forma de estrella. Puedes decidir usar un cuchillo para cortar manualmente las estrellas, o puedes decidir usar un molde, presionar 100 veces, y conseguir el mismo resultado de forma más eficiente.
Pongamos un ejemplo real: en todas las herramientas que he creado se tomó la decisión estética de crear un logo, una marca personal que aparece tanto como icono del ejecutable como una especie de miniatura en el margen izquierdo de todas las ventanas que pueden llegar a abrirse utilizando el programa.
Junto con ese logo, coexisten muchos otros que sirven o bien para adecentar estéticamente la interfaz o bien para representar botones como "copiar":
Con tal de optimizar el uso de esos logos, se requiere una función que le diga al programa qué imagen quiere usar y donde se encuentra guardada esa imagen dentro del sistema.
Si no utilizáramos la reutilización de funciones, tendría que escribir las rutas de carpetas y los comandos de carga de imagen en todas y cada una de las ventanas del asistente. Si el día de mañana decidiera cambiar ese logo por uno con un diseño más moderno, tendría que rastrear docenas de archivos para modificar la ruta de la imagen uno por uno. Es un trabajo tedioso donde es casi seguro que olvidaría actualizar alguna ventana, rompiendo la estética del programa.
Sin embargo, al aplicar la reutilización, he creado una única función llamada get_ubicacion_recurso. Esta pieza de código vive en un módulo central y es la única que sabe exactamente dónde se guardan los iconos y las imágenes del sistema.
Ahora, cuando cualquier ventana necesita mostrar el logotipo, simplemente llama a esa función compartida. Al instante, todas las ventanas de la aplicación, sin que yo tenga que tocar ni una sola línea de código adicional, mostrarán la nueva marca personal.
Esto es lo que permite que el programa sea robusto, coherente y, sobre todo, fácil de mantener a largo plazo.
La escalabilidad como pilar fundamental
Otro de los grandes peajes que pagué por mi inexperiencia fue la falta de escalabilidad. En el mundo de la programación, que algo sea escalable significa que está preparado para crecer sin tener que rediseñarlo desde cero.
Pongamos un ejemplo: mi primera propuesta de herramienta de conformidad únicamente permitía gestionar acuerdos que no superaran 4 acusados y 4 delitos por acusado. Si el caso judicial presentaba cinco, el programa simplemente no servía. Había construido una herramienta que solo funcionaba si la realidad se ajustaba a mis previsiones.
Recuerdo perfectamente un día en el que tuvo lugar mi récord de partes en una conformidad: 5 acusados y, para 4 de ellos, 5 delitos por acusado. En aquel momento, me di cuenta de que mi programa tenía un problema de escalabilidad.
Programar de esa forma es una condena a la rigidez, pues estaba creando un software que, en lugar de ayudar al juez a gestionar la complejidad de la realidad, le obligaba a recortar esa realidad para que cupiera en la máquina. Si el procedimiento era voluminoso, mi programa se convertía en un obstáculo más en lugar de una solución, obligándome a volver al papel o al Word de toda la vida porque el código no daba de sí.
Y eso era solo la punta del iceberg en lo que a carencias de escalabilidad se refiere, pues también había graves problemas en la forma en la que creaba los objetos, pues por aquél entonces aún no tenía mucha idea del uso de bucles y de la creación dinámica de objetos.
Así, para el Acusado 1, creaba de base los objetos Delito 1.1, Delito 1.2, Delito 1.3 y Delito 1.4., y luego repetía lo mismo para el Acusado 2, para el 3 y para el 4, con lo que al final tenía una maraña de dieciséis objetos fijos. Esto era ineficiente por muchos motivos, pero sobre todo porque esas casillas estaban siempre ahí, ocupando memoria y espacio, aunque el caso fuera contra un solo acusado por un solo delito. Había construido un formulario de hormigón: si el caso real presentaba cinco acusados, mi programa era basura porque no tenía donde meter el quinto.
La verdadera libertad llegó cuando aprendí a usar los bucles. Para alguien que no ha programado nunca, un bucle suena a algo cíclico o a un error de la máquina, pero en realidad es la instrucción que permite que el ordenador trabaje de forma incansable siguiendo una regla. Es, básicamente, decirle a la máquina: repite esta acción mientras se cumpla esta condición.
Gracias a esto, descubrí que no tenía que crear las casillas yo mismo, sino que podía dar una orden doble: por cada acusado que haya en el expediente y, a su vez, por cada delito que se le impute, crea una casilla en la pantalla.
Es un cambio de paradigma total. Ya no escribía dieciséis líneas de código para dieciséis cuadros fijos, sino una sola instrucción que se adaptaba al caso concreto.. Si el usuario selecciona que hay seis acusados, el bucle se encarga de dar seis vueltas y generar los espacios necesarios al vuelo.
Si solo hay uno, solo genera uno. Los objetos ya no están siempre creados, sino que nacen dinámicamente según la necesidad del procedimiento. Es pasar de un formulario rígido a un software que se estira o se encoge solo, permitiendo que la herramienta sea un aliado que se adapta al expediente y no una limitación que te obliga a simplificar la realidad para que quepa en la pantalla.
Gracias a esta lógica de repetición, mis herramientas pasaron de ser muebles estáticos a convertirse en estructuras elásticas. Hoy, si abres el gestor de penas, el programa no tiene un límite fijo de cuatro o cinco campos. El software utiliza esa instrucción para generar los espacios necesarios de forma instantánea, ya sea para un solo delito leve o para una causa compleja con veinte penas.
La escalabilidad es, en definitiva, lo que permite que la herramienta crezca o se encoja según la necesidad del momento sin que yo tenga que tocar ni una sola línea de código adicional. Es pasar de una informática que pone límites a una que ofrece soluciones para cualquier tipo de procedimiento, por inusual o voluminoso que sea.
Ordenando y comentando el código
Una de las lecciones que he tenido que asimilar en mi camino como programador amateur es que escribir código no consiste solo en darle órdenes a una máquina para que calcule una pena o abra una ventana. Eso es, en realidad, la mitad del trabajo.
El verdadero arte de la programación (y el que, con diferencia, más se me escapa) se libra en la estructura y en la capacidad de que ese código sea legible para un ser humano, empezando por uno mismo.
Al principio, mi forma de trabajar era puramente impulsiva: si necesitaba una función nueva, la escribía en el primer hueco que encontraba. El resultado era un archivo donde las instrucciones aparecían en un orden totalmente aleatorio, obligándome a saltar de la línea cien a la quinientas para entender cómo se conectaba un botón con su lógica.
Ahora, en lugar de lanzar las líneas al azar, trato de que el código siga una jerarquía que permita leerlo de forma natural: primero las importaciones necesarias, luego la definición de las clases que dan vida a la ventana y finalmente el bloque de funciones que responden a los clics del usuario, organizada según la funcionalidad que persiguen (funciones de guardado, funciones de interfaz, funciones de gestión de datos...)
Junto con el desorden visual, coexistía otro gran problema: la ausencia de comentarios. En un inicio casi veía esas líneas verdes que empiezan por una almohadilla no como un estorbo pero sí como algo innecesario.
Sin embargo, lo cierto es que cuando Picasso intentaba modificar algo en mi parte para echarme una mano se encontraba con un muro infranqueable porque no entendía mis atajos ni mis nombres de variables. Trabajábamos en el mismo proyecto pero hablábamos idiomas distintos, lo que generaba una frustración enorme y nos obligaba a darnos explicaciones constantes por teléfono que se habrían solucionado con una simple nota en el código. Pero es que incluso con el simple paso del tiempo ni yo mismo era incapaz de recordar qué hacían mis propias funciones.
Por ello, ahora me obligo a escribir una breve explicación que detalla para qué sirve cada función y qué variables concretas utiliza. Esos comentarios y ese orden jerárquico son los que me permiten retomar el trabajo donde lo dejé sin sentir que empiezo de cero.
La falsa seguridad de la IA
En este proceso de aprendizaje, uno de los muros más importantes con los que me he chocado es el de la excesiva confianza en la Inteligencia Artificial. Tal y como he explicado en otras entradas, hasta que empecé a cursar estudios de programación a través de varias universidades, mi principal fuente de conocimiento y aprendizaje era la IA. Ello hizo que, al menos al inicio, fuera excesivamente dependiente de ella, delegando en sus respuestas gran parte de la lógica que todavía no alcanzaba a comprender por mi cuenta.
Sin embargo, he aprendido por las malas que la IA no es infalible y que, en muchas ocasiones, propone soluciones que son del todo ineficientes o absurdamente complejas para el problema que tienes entre manos.
Recuerdo perfectamente una ocasión en la que estaba bloqueado con una función que no conseguía integrar. Le planteé el problema a la IA y me propuso crear un módulo entero nuevo, con sus importaciones, su lógica independiente y una estructura que añadía una capa de complejidad innecesaria a mi programa.
Tras perder un buen rato intentando integrar aquella montaña de código, me paré a mirar lo que ya tenía escrito y me di cuenta de la realidad: todo aquel despliegue se solucionaba añadiendo un simple else en una función que ya existía.
Hoy en día, prácticamente cualquier persona puede pedirle a una IA que le cree un programa ejecutable, pues basta con tener una idea medianamente clara y saber transmitirle lo que quieres, pero si no eres capaz de entender realmente qué está haciendo la IA con tu código, te conviertes en un rehén de su lógica.
Si no comprendes las entrañas de tu programa, no vas a ser capaz de modificarlo, no podrás escalarlo y, lo que es peor, serás totalmente incapaz de solucionar los errores que inevitablemente se van a generar.
La IA es una herramienta de apoyo extraordinaria, pero para programar herramientas que funcionen no basta con saber pedir cosas a la IA: hay que saber entender las respuestas que te ofrece.
Como conclusión...
A modo de conclusión, espero que esta entrada haya ayudado a comprender que entre crear herramientas que simplemente funcionen y desarrollar software de forma profesional hay un salto increíble. Es facil usar herramientas sin ser consciente de que la verdadera ingeniería reside en lo que no se ve: en esa arquitectura interna que permite que el código sea mantenible, seguro y duradero.
Soy plenamente consciente de que apenas he rozado la punta del iceberg en este vasto mundo de la programación, pues conceptos como la modularización, la escalabilidad o el respeto a la lógica interna son solo los primeros peldaños de una escalera que parece no tener fin. Por ello, asumo que mis herramientas no son más que proyectos de un aficionado y que se encuentran años luz de lo que un verdadero equipo de profesionales podría hacer.
No obstane, existe una verdad incontestable, y es que los programas que he creado no compiten con herramientas hechas por profesionales, esencialmente porque no existen tales herramientas. Mis proyectos compiten contra la ausencia de medios tecnológicos, contra una hoja en blanco de word y contra el sistema arcáico de copiar y pegar.
En tal contexto, tengo el convencimiento pleno de que lo que hemos puesto encima de la mesa supone un salto tanto cualitativo como cuantitativo que cualquier persona es capaz de advertir.
0 Comentarios