Debemos pensar antes de programar. Para esto se puede hacer uso de diferentes técnicas, algunas ya presentadas, como la verificación formal. O, en otros casos, podemos simplemente definir la solución a nuestro problema haciendo una demostración matemática que indique su comportamiento para cada argumento de entrada junto a su salida.
Leslie Lamport comenzaba diciendo su provocador artículo «If You’re Not Writing a Program, Don’t Use a Programming Language» («Si no estás escribiendo un programa, no uses un lenguaje de programación») lo siguiente:
He trabajado con varios ingenieros informáticos, tanto de hardware como de software, y he visto lo que sabían y lo que no. He descubierto que la mayoría de ellos no entienden algunos importantes conceptos básicos. Estos conceptos son oscurecidos por los lenguajes de programación. […] Consideraré los algoritmos, no los programas. Es inútil tratar de distinguir con precisión entre ellos, pero todos tenemos la idea general de que un algoritmo es una abstracción de nivel superior que es implementada por un programa. (Lamport, 2018)
Lamport se da cuenta de una falencia en la era actual del desarrollo de software, y es que la mayoría de los informáticos piensan en programas (una implementación) y no en algoritmos (una abstracción de nivel superior, es decir, especificación). Esto, él mismo lo atribuiría a una débil formación en matemáticas en comparación con otras carreras del ámbito de las ciencias.
Aunque nosotros agregaríamos un factor más a esta ecuación: el tiempo. Porque en la actualidad la cantidad de desarrollo de software ha crecido en una magnitud tal que hace casi imposible dar respuesta a cada nuevo proyecto que se comienza; es por esto que siempre se dice que faltan desarrolladores de software para una demanda que crece cada día. Cuando se necesita desarrollar más software en menos tiempo, la calidad se ve afectada y, era de suponer, la especificación tiende a verse más como un enemigo en contra del tiempo en busca de un objetivo que como una fase que asegura la calidad de lo que hacemos.
En la actualidad, la industria de software se mantiene embarcada solo en la verificación dinámica, o sea, en las pruebas unitarias o cualquier otro tipo de prueba a posteriori. Se crean departamentos de QA («Quality Assurance» en inglés [«Aseguramiento de la calidad»]) y a algunos programadores y programadoras ya ni les interesa realizar pruebas de lo que hacen, más bien, prefieren terminar rápido en lugar de terminarlo bien. Esto viene provocado por su falta de rigor y por la variable tiempo.
Debemos volver a los fundamentos, no igual que antes, ya que la industria ha evolucionado, pero sí con un enfoque renovado que no omita la teoría en desmedro de la calidad. En desmedro de, por así decirlo, nosotros mismos.
2.3 IMPLEMENTACIÓN
Cuando sabemos cómo resolver un problema computacional, entonces podemos buscar la forma de confrontarlo, haciendo uso claro de la tecnología más acorde en cuanto a nuestro tiempo y problema en cuestión. Es importante en esta fase no caer en el error clásico de forzar una tecnología para que resuelva todos los problemas que tengamos. Una cosa es que alguien tenga una afinidad hacia alguna (por ejemplo, Python) y otra es querer resolver todo con Python omitiendo cualquier otra tecnología (a saber, otro lenguaje de programación).
Cuando tratamos con la implementación lo que buscamos es crear un programa computacional. Es aquí donde debemos preocuparnos de aspectos que van al detalle del problema a tratar. Desde elegir correctamente la tecnología, pasando por la elección correcta del paradigma de programación, hasta cómo diseñar el código, por ejemplo: ¿qué estructura de datos o biblioteca podría usar? o ¿cuál es más conveniente, un lenguaje de programación tipado o no? Y si el problema es más complejo, pueden surgir preguntas de arquitectura de software: ¿cómo puedo diseñar el programa para que pueda aumentar la cantidad de datos que maneja en el tiempo sin producir cuellos de botella?, ¿cuáles serán los componentes del software y cómo se comunicarán entre ellos?, ¿cuáles serán las fases de despliegue de mi aplicación en la nube?, ¿cómo debo diseñar el software para que pueda soportar una carga de usuarios que aumentará en el tiempo?, etc.
La implementación no es simple, no se trata de eso. Sin embargo, lo que creemos es que muchas veces vamos directamente a la implementación cegados por la emoción de elegir las tecnologías, y olvidamos pensar cómo podríamos resolver el problema desde una visión mucho más abstracta, amplia y general (que para algunos problemas [los más complejos] suele ser lo importante). Pero también tenemos claro que para esto es necesaria la experiencia, la cual tiende a ser casi nula en desarrolladores novatos.
Una correcta implementación debe venir respaldada de un amplio conocimiento de múltiples herramientas y proyectos de software desarrollados previamente, para ver el bosque y no solo un árbol en particular. Así evitaremos el arrebato hacia la tecnología (aunque esto, lamentablemente, no siempre está asegurado). Con ello, nos aseguramos de reducir el margen de error que conlleva elegir tecnologías inadecuadas para el problema a solucionar.
Ahora definiremos algunos conceptos que son parte de la implementación.
Estructuras de datos. Una estructura de datos como tal es parte de una implementación, porque es una abstracción que permite organizar y manipular datos. Cada una de ellas tiene características para abordar distintos tipos de problemas computacionales de la manera más acorde y eficaz. Muchas veces están agrupadas por la «complejidad algorítmica», que es la forma de medir la eficacia de un algoritmo según los valores de entrada, ya sea por tiempo de ejecución o por espacio a utilizar (disco duro o memoria RAM).
La elección apropiada de las estructuras de datos puede marcar el éxito de un proyecto de software, no tan solo por temas de eficiencia, sino también por temas como la facilidad para expandir y ampliar el propio código. Si, por ejemplo, necesitamos acceder a la información de un cliente que tiene un identificador único, lo correcto sería usar una tabla hash para acceder directamente a su información y no, claro, usar una lista para encontrar ese identificador, ya que se debería recorrer toda la estructura según la cantidad de clientes que se tenga disponible (búsqueda lineal), lo cual es, por definición, ineficiente para ese problema.
Por eso es importante estudiar, analizar, experimentar y comparar las distintas estructuras de datos, para, así, evitar caer en errores como elegir la incorrecta estructura para un problema. (Por añadidura, debemos decir que las estructuras de datos se analizan en conjunto con el algoritmo a utilizar, pues un algoritmo puede estar construido con diversas estructuras de datos.)
En este libro dedicamos el capítulo 13 a diversas estructuras de datos que pueden ser utilizadas siguiendo un enfoque funcional.
Cada lenguaje de programación trae un conjunto de estructuras de datos estándar preestablecidas que nos facilitarán la implementación. Por ejemplo, algunas de ellas son: listas, cola, pila, grafos, árboles, tabla hash, entre otras.
Programa computacional. Un programa computacional existe cuando es ejecutado en un ordenador, es decir, necesita estar escrito en un lenguaje de programación. No es puramente una construcción abstracta sin experimentación (esto lo diferencia de algo escrito en el papel). El programa representa la parte final de la solución a un problema; es ahí donde comprobamos si nuestra solución funciona correctamente en un ambiente real y productivo.
Además, un programa es, a su vez, un proceso, si asumimos que un proceso es un programa que se está ejecutando en otro programa, a saber: un sistema operativo. (No obstante, este nombre podría variar según el sistema operativo