El software existe para proporcionar valor a alguien. Generalmente a nuestros clientes o empleadores. Como desarrolladores, se nos paga para construir software de la forma más efectivamente posible, para maximizar ese valor.
Como profesionales, tratamos de que nuestro código haga lo que tenga que hacer, y que sea fácil de mantener y mejorar a futuro. Para eso, hacemos uso de Patrones de Diseño que son, en esencia, soluciones comprobadas ante problemas habituales, que sirven como guía para tomar decisiones al momento de construir software.
Muchas veces los conocemos a través de mini frases, o eslóganes, como Don’t Repeat Yourself, Don’t Reinvent the Wheel, Keep It Simple, Stupid, Composition over Inheritance, y varias otras.
Todo esto tiene un punto, pero para eso quiero arrancar con dos de estas frases: DRY y Don’t Reinvent the Wheel.
DRY
DRY, ó Don’t Repeat Yourself, es un principio de diseño de software bastante conocido. La idea general es que la repetición de código debe evitarse, ya que dificulta mantener una pieza de software, a medida que esta va creciendo.
La lógica tiene sentido, si duplicamos código, a la hora de cambiarlo o arreglarlo, uno tiene que acordarse de arreglarlo en todos los lugares donde este se usa, y cada uno de estos lugares puede que tenga comportamientos diferentes. Esto conlleva VARIOS problemas:
- Puede que te olvides de aplicar un cambio en alguno de los lugares repetidos.
- Probablemente, va a llevar más tiempo al tener que aplicarlo (y probarlo) en todos los lugares donde se repita.
- Puede que al aplicar el cambio, lo apliques de la misma forma en código que tiene o contextos diferentes, o comportamientos esperados diferentes.
- Ligado al anterior, puede no ser inmediatamente obvio el hecho de que dos piezas de código hagan lo mismo, pero estén en lugares diferentes.
Okay, te vendí un montón el porqué aplicar DRY, del porqué evitar la duplicación de código. ¿Entonces cuál es el problema?
Bueno, de lo que no se habla mucho, es que aplicar DRY (y generalmente de la aplicación de patrones de diseño), conlleva una serie de desventajas:
Problema 1 - Acoplamiento
¿Qué pasa cuando tu Manager, o el equipo de Producto o UX, decide que de los 35 inputs que hiciste, quieren que uno (solo uno) en particular se comporte de forma diferente?
Si, el código DRY, generalmente es código más acoplado. ¿Se puede resolver esto? ¡Seguro! Podemos agregar ifs por todos lados, o implementar otros patrones para solucionarlo de forma prolija, pero siempre a costa de más complejidad.
El código, al unificarse, no puede evolucionar de forma independiente, es generalmente un todo o nada.
Problema 2 - Complejidad y Tiempo
¿Cuánto tiempo te lleva hacer un componente genérico, frente a uno bien especifico para un caso particular? No me arriesgo al decir que el genérico, que contempla más casos, te lleva más tiempo.
Hacer componentes genéricos, conlleva hacer componentes que son más versátiles. Esto en la gran mayoría de los casos lleva más tiempo que hacerlos específicos para el caso a considerar.
El aumento de complejidad no solo afecta al tiempo de desarrollo inicial, sino a la dificultad de manutención de los componentes:
Dale la tarea a un junior de agregar una validación extra a esa función que genera CRUD automáticos de forma mágica en el backend hace meses, y miralo llorar.
Y sí, seguro de que el generador de Forms genérico que se usa en toda la empresa funciona bien, pero si le toca hacer un cambio que le pidieron al nuevo, de que algunos mensajes de error estén en mayúscula y en violeta, le va a llevar mucho más tiempo.
La complejidad es un concepto un poco difícil de explicar. A efectos prácticos, digamos que es la tasa de “Que Carajo” por minuto que te genere leer una pieza de código.
Aumentar la complejidad de una pieza de software, implica aumentar la curva de dificultad de un nuevo desarrollador en el equipo (o de un junior/semi-senior) a la hora de entender y poder mantener una pieza de código. Esto no es poca cosa, porque de nuevo: se nos paga para hacer código mantenible y mejorable.
Cambiemos de eslogan antes de que me tiren con algo. Voy a volver a este tema al final.
Don’t Reinvent the Wheel
Que pasa si tomamos DRY, y lo aplicamos en todo un ecosistema de desarrollo de software?. No reinventar la rueda (Don’t Reinvent the Wheel) es otro eslogan que se suele escuchar bastante, relacionado con el anterior.
La idea es que como desarrolladores, teniendo en cuenta que nuestro trabajo implica producir software que genere valor a nuestros clientes, no deberíamos intentar hacer todo desde cero, sino reutilizar donde podamos el código.
Bastante parecido a DRY, pero con código de otros, ¿verdad?
La realidad es que la industria del desarrollo web, en parte, creció gracias a este patrón. La web está construida, desde el principio, sobre los hombros de gigantes. Sin el Open Source, y al ecosistema de aplicaciones que nos da NPM y Node, la web no sería lo que es hoy.
Las razones son simples: Dinero y Tiempo
Para una empresa emergente, el tiempo entre idea y MVP puede llegar a ser cortísimo, y el coste de alcanzar una audiencia grande, bastante bajo.
Con herramientas y productos como Wordpress, Wix, Squarespace o Shopify, una empresa puede levantar un proyecto en re poco tiempo de forma muy barata, y si nos vamos más a lo nuestro, los frameworks y librerías que usamos abstraen mucha de la complejidad que implica hacer una aplicación, reduciendo el tiempo y esfuerzo necesario para hacer algo hoy en día.
No solo eso, sino que cuando el código es probado, revisado, y mejorado por más gente, generalmente mejora.
React y Angular son mejores hoy que hace unos años, por simple hecho de que tienen gente interesada en ellos, que aporta con revisiones de código, bug reports, casos de uso, optimizaciones, tiempo, plata.
Esto va haciendo que la calidad, performance y seguridad mejore.
Pero, al final del día, este enfoque también tiene problemas:
Problema 1 - Es un montón de Código
Las librerías de las que dependemos generalmente hacen lugar para tantos, pero tantos casos de uso, que por más tree-shaking que hagamos, el código que se corre no es el óptimo, que no sea la herramienta correcta. Estamos enviando enormes cantidades de JavaScript a nuestros usuarios, en gran medida por incluir librerías y código que no nos hacen falta, no realmente.
Simplemente, porque ese código hace demasiadas cosas. Y el problema se va agravando año a año.
Esta es la cantidad de kilobytes de JavaScript enviados este último año en promedio desde hace unos 3 años a ahora.
Este número debería ir en descenso!. Las API de los navegadores se van haciendo mejores y más fáciles de usar, estamos creando herramientas para eliminar código muerto, pero la tendencia sigue en alza, y seguimos pusheando cada vez más código.
Problema 2 - Supply Chain Attacks (Ataque a Cadena de Suministro)
No solamente nuestro software puede terminar siendo más lento, sino que también puede ser menos seguro. Las dependencias abren nuevos vectores de ataque en nuestras computadoras, y a las de nuestros clientes y usuarios.
Traducción: Arriba: Toda la infraestructura moderna digital. Abajo: el proyecto de un loco cualquiera que lo viene manteniendo desde 2003.
A veces no tomamos en consideración, lo mucho que estamos confiando en otros programadores a la hora de ejecutar npm install (o sus equivalentes) en nuestras computadoras. El proceso de instalación de dependencias permite correr software aleatorio con un MONTÓN de permisos. Y cada vez se ve más y más software malicioso siendo subido a NPM.
Y cada vez pasa más seguido, que gente malintencionada (o perturbada) rompa nuestras apps o librerías, porque todo depende de todo, todo por No Reinventar la Rueda, aun cuando hay razones para hacerlo.
Y por si no me creen, les cuento de algunos casos reales:
- OpenSSL se encarga de firmar las peticiones y respuestas para cientos de millones de usuarios, en millones de servidores. A pesar de esto, era mantenida solo por 4 desarrolladores, de hobby nomás. Apareció un agujero de seguridad, y tardaron 3 años en darse cuenta de que un atacante podía, de forma bastante fácil, obtener mucha información confidencial.
- En otro caso, un desarrollador que había hecho como 250 paquetes de NPM, decidió sacarlos a todos del registro. Rompiendo por ello miles y miles de apps y librerías. Por primera vez en su historia, NPM republicó un paquete que había sido borrado por su creador. No fue la última vez que pasó algo así.
// Esto es, literalmente, toda la librería leftpad.
// Solo le agrega caracteres a un string
module.exports = leftpad;
function leftpad (str, len, ch) {
str = String(str);
var i = -1;
if (!ch && ch !== 0) ch = ' ';
len = len - str.length;
while (++i < len) {
str = ch + str;
}
return str;
}
- También hubo un tema con ESLint, donde una dependencia/plug-in muy popular, empezó a meter malware en la PC de muchos desarrolladores. El proceso de NPM install descargaba un archivo de texto, que era básicamente un virus que después ejecutaba.
Problema 3 - No entender cómo funciona la rueda
Más de una vez vi gente que en vez de filtrar un array, con los métodos de filtrado de array, importa lodash. Esto nos atrasa como profesionales, tenerle miedo a reinventar la rueda también implica que no tenemos incentivos para aprender cómo funciona. Aunque sea para aprender nomás.
Aclaraciones sobre WET y Zero-Deps
Antes de que me tiren con algo, voy a mencionar que ante DRY, existe WET, que propone siempre escribir todo código al menos dos veces antes de abstraer, para tener una imagen mejor formada de lo que se planea abstraer y simplificar. Es un approach que me gusta más, pero que tampoco seguiría a ciegas.
Frente a Don’t Reinvent the Wheel, hay librerías que se jactan de no tener dependencias y que por ello, sí, son inmunes a supply chain attacks, pero eso no implica que esas mismas librerías no vayan a ser las que tengan problemas de seguridad u otros problemas.
Conclusión
La solución no es un approach o el otro el 100 % del tiempo. No hay balas de plata. Todo patrón tiene implicancias positivas y negativas en su aplicación y entender el balance de pros y contras es parte de nuestro trabajo. Siempre hay una contra, aunque esta sea el aumento de complejidad de aplicación que desarrollamos.
El resultado neto de su aplicación suele ser positivo, especialmente para programadores junior, ya que funcionan como guía de estilo a seguir, frente a la incertidumbre. Para alguien que recién arranca, seguir un patrón a ciegas es mejor opción que avanzar a oscuras.
Pero es importante no ser dogmático al respecto.
A medida que vamos creciendo profesionalmente, tenemos que empezar a considerar los contras que tienen nuestras prácticas, a tomar las cosas con pinzas y de forma pragmática, y a tener en consideración los posibles problemas que pueden surgir, para tomar decisiones informadas, y preparados para las posibles consecuencias.
Eso es lo que me interesa que se lleven de este artículo, de que cuando vean uno de esos eslóganes, piensen “okay, pero por qué?”, y… “Como lo podría romper?”.
Eso es todo. Suerte! 🤙🏻
Si te quedaste con ganas de Leer más, y sabes inglés, acá van algunos artículos interesantes del tema:
- The Fallacy of Dry.
- Moist Code y AHA programming
- The Hidden cost of don’t reinvent the wheel
- The cost of Javascript 1 y 2.
- When to Reinvent the Wheel.
- When it’s okay to reinvent the wheel
- 10 Useles NPM packages with million
Créditos de las imágenes: Lukas Tennie, Ante Hamersmit, Jeppe Hove Jensen y XKCD Infraestructure comic