Hace no muchos años un buen programador se definía por su capacidad para hacer código eficiente. El hardware imprimía unas limitaciones importantes y el buen desarrollador era capaz de sacar más transacciones por segundo que los demás. Era una época en la que a veces te planteabas hacer un fragmento del código en ensamblador, para ganar en procesado, y en la que, más importante que tu código fuese de lectura elegante, era que estuviese optimizado para el compilador. Era la época de los pragmas, mensajes para que el compilador hiciese mejor su trabajo; o la de pensarse complejas SELECTs para sacar los datos de la base de datos de la forma más eficiente posible. Los tornillos (hardware) eran caros, y salía rentable invertir más neuronas (horas de desarrollo) en el proyecto y aumentar segundos (duración del proyecto) con tal de mejorar la eficiencia.
Recuerdo que durante mi etapa en la universidad un compañero desarrolló para un concurso un juego similar al Tetris. El juego se movía en color de forma muy fluida en un 386 y eso me asombró bastante. Mi admiración máxima vino cuando me dijo que lo había desarrollado 100% en ensamblador. Esa era la definición de crack en aquel momento.
Con el paso del tiempo, el precio del tornillo ha ido bajando. Por cada euro cada vez se saca más potencia de procesado, con lo que también han aumentado las posibilidades. Negocio puede pedir ahora cosas que antes ni se planteaba. Con lo que en vez de disminuir el tamaño de los centros de procesado de datos, estos han ido creciendo. Los servidores son más potentes, pero en vez de contentarnos con menos, nos pasa lo contrario, queremos más.
Las neuronas en cambio siguen valiendo lo mismo. Pero la complejidad de los proyectos ha ido aumentando, así como la demanda de sus servicios, por lo que la cantidad de neuronas necesarias aumenta. El coste en neuronas de los proyectos es cada vez más importante en la cuenta de gastos.
En cuanto a los segundos, han ido cobrando valor. Las cosas cambian cada vez más rápido y el time-to-market se convierte cada vez es más importante. Cada segundo de retraso en la entrega de un proyecto es una oportunidad de negocio perdida.
Combinando estos tres aspectos llegamos al punto de inflexión en el que los beneficios de la eficiencia pasaron a ser menores que los costes en neuronas y en segundos. Resumiendo, las prioridades a partir de ese momento son: menos horas de programación y menor tiempo de duración del proyecto de desarrollo. Eso sí, manteniendo, o incluso aumentando, las garantías de funcionalidad y seguridad.
Cada año que pasa, esta relación entre el coste del tornillo frente a las neuronas y los segundos, se ha ido agrandando, con lo que el efecto en la manera de llevar los proyectos de desarrollo y las herramientas para el desarrollo, también van cambiando. Vemos como las técnicas ágiles se van haciendo un hueco en un intento en reducir el time-to-market. Y tecnologías que cuando aparecieron se tacharon de poco eficientes ahora son el Santo Grial del desarrollo de software empresarial, como Java.
Pero en donde he notado el cambio en mayor medida es en la aparición de los frameworks de desarrollo. Un framework, para un profano, podría definirse como “un programa a medias”. Sé que es una animalada lo que acabo de decir, pero para alguien que no los conoce creo que es una manera de aproximarse al concepto real de framework en una frase. Cuando desarrollas sobre un framework te despreocupas de las tareas comunes (y que son las que más CPU se llevan) para que las haga el framework. Además el framework te obliga a seguir una estructura en tu código que hace que esté ordenada y clara para el programador que venga detrás. Esto provoca, desde mi punto de vista, efectos como los siguientes:
- El tiempo necesario para conseguir una determinada funcionalidad se ha reducido enormemente. En algunos caso podemos hablar de un orden de magnitud (dividir por diez).
- El código resultante es mucho más fácil de mantener, con lo que se reduce también el tiempo necesario de mejoras o correcciones posteriores.
- El código resultante es muy ineficiente, pero fácilmente escalable. Lenguajes de scripting o interpretados salen adelante.
Pongamos un ejemplo de como ha evolucionado el solicitar una serie de consultas a base de datos:
- La primera tendencia para conseguirlo fue la de meter directamente el código en la propia base de datos. El desarrollo en PL/SQL o lenguajes similares es el más eficiente en toda la cadena, ya que está junto a los datos. Pero su desarrollo y mantenimiento es muy costoso.
- Después pasamos a las capas de acceso a datos en las que la base de datos deja de ejecutar lógica de negocio y ésta pasa toda a un lenguaje más fácil de entender. Las consultas las escribe el programador, pero en un entorno más agradable (e ineficiente, claro).
- Actualmente las estrategias están en no hacer capa de neogio, que ya me la hace el framework. Yo quiero pedirle al framework que me dé un dato de un empleado, y éste decide que SELECT montar y me lo entrega. Las SELECTS que se generan son 100% funcionales y consiguen el objetivo, pero muchas veces no son las más eficientes y piden más datos que los que necesitan o cuestiones similares. Ahora el programador se ahorra desarrollar la capa de negocio, a cambio de perder eficiencia del código.
Y esto también se ve en los lenguajes. Los lenguajes tradicionales, como C o C++ son 100% declarativos. Están creados desde el punto de vista del compilador. Es decir pensando en qué código ensamblador se generará desde ese C y que este código sea eficiente. Java en cambio, aunque sigue muy ligado al ensamblador, comienza a añadir conceptos que el programador no podrá controlar, como por ejemplo el recolector de basura. En C, si un objeto dejaba de ser útil el programador debía liberar su memoria manualmente. En Java, el recolector ya se da cuenta de que un objeto ya no es útil sin que el programador lo diga, y el recolector libera la memoria. Con lo que el programador ya no se preocupa de esta tarea. En los lenguajes más modernos, como Ruby el lenguaje se crea desde el punto de vista del programador y no del código maquina que generará. Así encontramos un código muy fácil de leer pero muy poco eficiente.
Los frameworks más modernos llevan esta teoría a extremos que hace 10 años no podríamos haber comprendido. Tenemos a Ruby on Rails, que obliga al desarrollador a ser ordenado, y minimiza el código necesario. Hace unos meses le eché un vistazo al código de RedMine, desarrolaldo en Rail. Tarde escasos minutos en ver como estaba estructurado y creo en un par de horas ya podría haber estado desarrollando el custom que necesitaba. Haz lo mismo en un desarollo en C. Imposible. También tenemos a AngularJS en el que la aplicación se mueve al navegador. Desarrollar en AngularJS es muy, muy cómodo. En momentos consigues cosas espectaculares en el lado del cliente. Pero la contra vuelve a ser la eficiencia. Una aplicación AngularJS sobre un navegador consume unas 100 veces más memoria que su contrapartida en cliente pesado. ¡Pero qué mas da! Las neuronas que ahorro en su desarrollo y la facilidad de mantenimiento (que al final son neuronas), así como los segundos que me ahorro en salir a producción justifican con creces el gasto extra en tornillos.
Ahora si me encontrase con un programador que me dice que ha hecho un tetris 100% en ensamblador le daré el mismo mérito que a uno que hace una torre Eiffel con palillos: un hobby curioso, un reto que en el fondo es una pérdida de tiempo. Lo que antes era un crack, ahora es un freaky.
En lo personal considero mejor un balance de eficiencia y productividad.