jueves, 23 de julio de 2015

La escencia de la programación de computadores

El gran Brian Kernighan escribió una vez, hace como mil años (circa 1976), la siguiente frase:

"Controlling complexity is the essence of computer programming."

Una posible traducción sería: "El control de la complejidad es la esencia de la programación de computadoras." Debo haber leído esta cita por primera vez hace al menos 10 años. En su momento me pareció una más. No eran palabras rimbombantes, ni conceptos extraños. Parecía más bien un punto de vista, una opinión, con algo de cierto, algo de exagerado, como tantas otras. Otras frases me impactaron/gustaron mucho más cuando las leí por primera vez. Sin embargo, con esta pasó algo muy especial. Con el tiempo, muuuy lentamente, a medida que cometía mis propios errores e iba aprendiendo con cada uno de ellos a programar un poquito mejor, me fui dando cuenta de que esa no es una frase más, sino una de las verdades más profundas e indiscutibles que podemos enunciar sobre la programación.

Ahora cito a Kernighan en mis clases, al explicar cómo analizar un problema antes de siquiera iniciar el IDE, al explicar cuándo y para qué usar los mecanismos de abstracción (como por ejemplo funciones u objetos), al explicar cuáles son los problemas reales a los que se enfrenta un programador en su día a día, al discutir cuáles son los aspectos importantes de un lenguaje, o hasta de un paradigma...

Veo, por ejemplo, cuando me toca lidiar con mis códigos legacy (heredados de mí mismo :P, de un yo del pasado todavía más verde) errores de diseño que podría decirse que obedecen mayormente a un problema de objetivos. El diseño puede ser medianamente correcto, solo que a la luz de un objetivo particular, que ya no me parece tan correcto. El principal objetivo ahora es atacar y reducir la complejidad, porque veo que los demás requisitos pasan a ser secundarios, y se resuelven solos como consecuencia del primero. Strosutroup nos demostró con C++ que podemos tener abstracciones de alto nivel, pero de costo-cero, en términos de performance, o de consumo de recursos en general. Entonces, primero busco mecanismos de abstracción que me permitan reducir la complejidad del problema, y luego las herramientas para implementarlos sin contradecir los demás objetivos. Las demás bondades, como mucha cohesión, poco acoplamiento, simplicidad, legibilidad, mantenibilidad, reusabilidad, etcétera-bilidad, incluso performance, vienen casi que solas.

Algunas dudosas medidas que ofrece ZinjaI sobre el tamaño de un proyecto, en este caso el propio ZinjaI.
No todo lo escribí a mano, varios cientos de locs son generadas automáticamente por scripts.

Y toda esta reflexión viene a cuento de dos situaciones en particular. Primero, que recuerdo que escuché en una charla (o leí en algún artículo, porque aunque no encuentro exactamente cual) que un buen programador puede entender y manejar completamente por sí mismo un proyecto de hasta más o menos 50k lineas de código. Sí, claro, "líneas de código" no es una medida decente, de hecho es horriblemente incorrecta. Pero permítanme la licencia para decir que siendo el "¿cuan grande puede llegar a ser mi proyecto antes de que se me vaya de las manos?" una medida muy poco exacta, esto podría ser en algún contexto una primera aproximación.

Y de hecho, mi no tan basta experiencia concuerda bastante con esta endeble idea, considerando que ZinjaI está desde hace un tiempo rondando muy de cerca ese límite (todo depende de cómo se mida). Últimamente (y no por culpa de esa afirmación) cada vez que agrego algo, aplico también un mayor o menor refactory interno para reducir la complejidad de agregar ese algo, y mantener así el proyecto "a raya" (lo cual casualmente viene ser cerca de ese límite). Todavía puedo tener en la cabeza un panorama global de cómo funciona todo, y poner en mi caché interno cuanta cosa necesite para agregar o modificar alguna funcionalidad. Pero si quiero que siga creciendo, tengo que reducir la complejidad en varias partes, o ya no podré predecir hasta dónde impacta un cambio, o dónde debo tocar para añadir algo.

La segunda situación que me llevó a escribir esta reflexión, fue la siguiente: dada la primera, me puse a ver qué herramientas tenemos para medir la complejidad de un código. Hay herramientas que hacen análisis estático de código y extraen ciertas medidas objetivas, relacionadas a la complejidad ciclomática, al nivel de anidamiento de estructuras, a la cantidad de caminos que puede tomar la ejecución, las interrelaciones entre clases, etc. Entre las pocas que vi, encontré una muy simple, que da una buena medida. Es un script de python, llamado lizard, que analiza la complejidad ciclomática aparente de cada función, considerando solo lo que puede verse en su archivo (no sigue #includes, ni preprocesa). Entonces, aproxima muy bien cuan compleja se ve una función a ojos del programador, sin importar cuan compleja realmente sea para el compilador o para la ejecución.

Resultados de aplicar lizard al código fuente de ZinjaI. Las dos primeras columnas
listan cantidad de lineas de código y complejidad ciclomática para cada función.

Lo probé con proyectos míos y vi resultados muy consistentes con mis propias impresiones sobre dichos proyectos. Por ejemplo, en el nuevo engine de MotoGT2 (al cual considero mi mejor código hasta la fecha), ninguna función supera la complejidad que el autor considera límite como para generar una advertencia, que es 15 de sus unidades. Sin embargo, en las partes más viejas de ZinjaI y PSeInt tengo muchas funciones que miden hasta por encima de 100, y que me consta que son una locura. Otro ejemplo fue el código de mi tesis doctoral, un código más complejo pero bastante reciente, que he analizado parte por parte con mucho más detalles y herramientas, y los resultados de lizard también tienen correlación directa con la realidad.

Y ya que la herramienta es libre, no requiere mucho trabajo, y parece andar muy bien, la integré en el menú de herramientas de ZinjaI en la última versión. Por ahora ZinjaI simplemente se encarga de invocarla como corresponda y presentar los resultados en un par de tablas con facilidades para el filtrado y ordenamiento. Tal vez más adelante genere algunas representaciones gráficas con esos datos (grafos, histogramas, algo de eso). El esfuerzo para entender, validar/testear y depurar un proyecto también está directamente relacionado con estas medidas. Entonces, espero que esto les ayude a identificar dónde deben centrar sus esfuerzos a la hora de hacer refactory para mejorar, simplificar, desacoplar, documentar o hasta modernizar una buena base de código. Y confirmen que a larga, es una inversión de tiempo muy fácil de amortizar.

1 comentario:

  1. Excelente aporte.
    Me llamó la atención el enfoque y decidí probarlo en mis proyectos.
    Coincido con las mediciones y las conclusiones.

    ResponderEliminar